skills/dimdasci/vps-setup/caddy-reverse-proxy

caddy-reverse-proxy

SKILL.md

Caddy Reverse Proxy

Caddy is a web server with automatic HTTPS. It obtains and renews certificates automatically, redirects HTTP to HTTPS, and provides a simple configuration syntax.

Quick Reference

Task Syntax
Basic proxy reverse_proxy backend:8080
Multiple upstreams reverse_proxy node1:80 node2:80
Path routing handle /api/* { reverse_proxy api:8080 }
Strip path prefix handle_path /api/* { reverse_proxy api:8080 }
Set request header header_up Host localhost
Remove response header header_down -Server
Wildcard cert tls { dns cloudflare {env.CF_API_TOKEN} }
Reload config caddy reload --config /etc/caddy/Caddyfile

Basic Patterns

Simple Reverse Proxy

example.com {
    reverse_proxy backend:8080
}

Multiple Sites

app.example.com {
    reverse_proxy app:3000
}

api.example.com {
    reverse_proxy api:8080
}

Path-Based Routing

example.com {
    handle /api/* {
        reverse_proxy api:8080
    }
    handle {
        reverse_proxy frontend:3000
    }
}

Strip Path Prefix

example.com {
    handle_path /api/* {
        reverse_proxy api:8080  # /api/users → /users
    }
}

Header Manipulation

example.com {
    reverse_proxy backend:8080 {
        # Set Host header (required by some backends)
        header_up Host localhost

        # Add client IP
        header_up X-Real-IP {remote_host}

        # Remove sensitive header
        header_up -Authorization

        # Remove server identity from response
        header_down -Server
    }
}

Docker Integration

Proxy to Container (Same Network)

# docker-compose.yml
services:
  caddy:
    image: caddy:2-alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - caddy_data:/data
    networks:
      - web

  app:
    image: myapp
    networks:
      - web
    expose:
      - "8080"
# Caddyfile - use container name as hostname
example.com {
    reverse_proxy app:8080
}

Proxy to Host Service

services:
  caddy:
    extra_hosts:
      - "host.docker.internal:host-gateway"
example.com {
    reverse_proxy host.docker.internal:8080
}

TLS Configuration

Automatic (Default)

Caddy automatically obtains certificates when:

  • Domain DNS points to server
  • Ports 80/443 accessible

Wildcard Certificates

Requires DNS provider plugin:

*.example.com {
    tls {
        dns cloudflare {env.CF_API_TOKEN}
    }

    @app1 host app1.example.com
    handle @app1 {
        reverse_proxy app1:8080
    }

    @app2 host app2.example.com
    handle @app2 {
        reverse_proxy app2:8080
    }
}

Internal/Development

localhost {
    tls internal
    reverse_proxy app:8080
}

Request Matchers

# Path matching
@api path /api/*

# Header matching
@websocket header Connection *Upgrade*

# Method matching
@post method POST

# Combined (AND logic)
@api_post {
    path /api/*
    method POST
}

# Use matcher
handle @api {
    reverse_proxy api:8080
}

Load Balancing

example.com {
    reverse_proxy node1:80 node2:80 node3:80 {
        lb_policy round_robin
        health_uri /health
        health_interval 30s
    }
}

Common Configurations

Security Headers

(security) {
    header {
        Strict-Transport-Security "max-age=31536000; includeSubDomains"
        X-Content-Type-Options nosniff
        X-Frame-Options SAMEORIGIN
        Referrer-Policy strict-origin-when-cross-origin
    }
}

example.com {
    import security
    reverse_proxy backend:8080
}

www Redirect

www.example.com {
    redir https://example.com{uri} permanent
}

SPA (Single Page App)

example.com {
    root * /srv
    try_files {path} /index.html
    file_server
}

gRPC / HTTP/2 Cleartext

example.com {
    reverse_proxy h2c://grpc-server:9000
}

Global Options

{
    email admin@example.com

    # Use staging for testing
    # acme_ca https://acme-staging-v02.api.letsencrypt.org/directory
}

example.com {
    # site config
}

Reference Files

File When to Read
reverse-proxy.md Load balancing, health checks, transport options
caddyfile-syntax.md Matchers, handle blocks, directives
tls-certificates.md DNS challenge, wildcards, mTLS
docker-patterns.md Docker Compose, networks, host access
troubleshooting.md Common errors and diagnostics

Common Issues

Problem Solution
"no upstreams available" Check backend running, same Docker network
"connection refused" Backend binding to 0.0.0.0, not 127.0.0.1
Certificate not obtained Check DNS points to server, ports 80/443 open
403 Forbidden Backend needs header_up Host localhost
WebSocket fails Usually works; check flush_interval -1 for SSE

Verification Commands

# Validate config
caddy validate --config /etc/caddy/Caddyfile

# Format config
caddy fmt --overwrite /etc/caddy/Caddyfile

# Reload (zero-downtime)
caddy reload --config /etc/caddy/Caddyfile

# Debug mode
# Add to global options: { debug }

# Check certificate
openssl s_client -connect example.com:443 -servername example.com

Official Documentation

Topic URL
Caddyfile https://caddyserver.com/docs/caddyfile
reverse_proxy https://caddyserver.com/docs/caddyfile/directives/reverse_proxy
Automatic HTTPS https://caddyserver.com/docs/automatic-https
Common Patterns https://caddyserver.com/docs/caddyfile/patterns
Weekly Installs
6
First Seen
Mar 1, 2026
Installed on
opencode6
gemini-cli6
github-copilot6
amp6
cline6
codex6