Skip to main content

Reverse Proxy Setup

Caddy’s reverse proxy is a production-grade, highly configurable module that supports load balancing, active and passive health checks, dynamic upstreams, and more.

Basic Reverse Proxy

Proxy requests to a backend server:
{
  "handler": "reverse_proxy",
  "upstreams": [
    {"dial": "localhost:8080"}
  ]
}

Multiple Upstreams

Load balance across multiple backends:
{
  "handler": "reverse_proxy",
  "upstreams": [
    {"dial": "10.0.0.1:8080"},
    {"dial": "10.0.0.2:8080"},
    {"dial": "10.0.0.3:8080"}
  ]
}

Load Balancing Policies

1
Available Policies
2
  • Random (default) - Randomly select an upstream
  • Round Robin - Cycle through upstreams sequentially
  • Least Connections - Send to upstream with fewest active connections
  • IP Hash - Consistent hashing based on client IP
  • Header - Hash based on request header
  • Cookie - Sticky sessions via cookie
  • {
      "handler": "reverse_proxy",
      "load_balancing": {
        "selection_policy": {
          "policy": "least_conn"
        },
        "retries": 3,
        "try_duration": "10s",
        "try_interval": "250ms"
      },
      "upstreams": [
        {"dial": "10.0.0.1:8080"},
        {"dial": "10.0.0.2:8080"}
      ]
    }
    

    Health Checks

    Active Health Checks

    Periodically check upstream health in the background:
    {
      "health_checks": {
        "active": {
          "uri": "/health",
          "port": 8080,
          "interval": "30s",
          "timeout": "5s",
          "passes": 2,
          "fails": 3,
          "expect_status": 200,
          "expect_body": "healthy",
          "headers": {
            "Host": ["example.com"],
            "User-Agent": ["Caddy-Health-Check"]
          }
        }
      }
    }
    
    Active health checks run on a timer and require at least uri or port to be set. They do not run for dynamic upstreams.

    Passive Health Checks

    Monitor proxied requests for errors:
    {
      "health_checks": {
        "passive": {
          "max_fails": 3,
          "fail_duration": "30s",
          "unhealthy_status": [500, 502, 503],
          "unhealthy_latency": "10s",
          "unhealthy_request_count": 10
        }
      }
    }
    

    Header Manipulation

    Request Headers

    Modify headers sent to the upstream:
    reverse_proxy backend:8080 {
        # Add headers
        header_up X-Real-IP {remote_host}
        header_up X-Custom-Header "value"
        
        # Remove headers
        header_up -Authorization
        
        # Set Host header
        header_up Host {upstream_hostport}
    }
    
    Caddy automatically sets X-Forwarded-For, X-Forwarded-Proto, and X-Forwarded-Host headers. You usually don’t need to set these manually.

    Response Headers

    Modify headers returned to clients:
    reverse_proxy backend:8080 {
        # Add or modify headers
        header_down Access-Control-Allow-Origin "*"
        header_down Server "Caddy"
        
        # Remove headers
        header_down -Server
    }
    

    Transport Configuration

    HTTP Transport

    {
      "transport": {
        "protocol": "http",
        "dial_timeout": "10s",
        "dial_fallback_delay": "300ms",
        "response_header_timeout": "30s",
        "expect_continue_timeout": "1s",
        "keep_alive": {
          "enabled": true,
          "idle_conn_timeout": "90s",
          "probe_interval": "30s"
        },
        "max_conns_per_host": 100,
        "compression": false
      }
    }
    

    TLS Configuration

    Connect to upstreams over HTTPS:
    reverse_proxy https://backend.internal:8443 {
        transport http {
            tls
            tls_server_name backend.internal
            tls_trusted_ca_certs /path/to/ca.pem
        }
    }
    

    Request Buffering

    Buffer requests/responses for slow clients or backends:
    {
      "handler": "reverse_proxy",
      "request_buffers": 4096,
      "response_buffers": 4096,
      "flush_interval": "100ms"
    }
    
    Request/response buffering can impact performance and memory usage. Only enable if your backend requires it.

    WebSocket Support

    Caddy automatically detects and handles WebSocket connections:
    reverse_proxy /ws/* backend:8080 {
        # Optional: set timeouts for long-lived connections
        stream_timeout 1h
        stream_close_delay 5s
    }
    

    Handle Response

    Intercept and handle upstream responses:
    reverse_proxy backend:8080 {
        @error status 500 502 503
        handle_response @error {
            respond "Service temporarily unavailable" 503
        }
        
        @redirect status 3xx
        handle_response @redirect {
            copy_response_headers {
                include Location
            }
            copy_response
        }
    }
    

    Trusted Proxies

    Define which proxies to trust for X-Forwarded-* headers:
    reverse_proxy backend:8080 {
        trusted_proxies private_ranges 10.0.0.0/8
    }
    

    Complete Example

    api.example.com {
        # Load balance across multiple backends
        reverse_proxy backend1:8080 backend2:8080 backend3:8080 {
            # Use least connections policy
            lb_policy least_conn
            lb_retries 3
            lb_try_duration 10s
            
            # Active health checks
            health_uri /health
            health_interval 30s
            health_timeout 5s
            health_status 200
            
            # Passive health checks
            max_fails 3
            fail_duration 30s
            unhealthy_status 500 502 503
            
            # Request headers
            header_up X-Real-IP {remote_host}
            header_up X-Request-ID {uuid}
            
            # Response headers
            header_down Server "Caddy Proxy"
            
            # Transport configuration
            transport http {
                dial_timeout 10s
                keepalive 90s
            }
            
            # Handle errors
            @error status 500 502 503
            handle_response @error {
                respond "Service unavailable" 503
            }
        }
    }
    

    Placeholders

    Available placeholders during reverse proxy:
    • {http.reverse_proxy.upstream.address} - Full upstream address
    • {http.reverse_proxy.upstream.host} - Upstream host
    • {http.reverse_proxy.upstream.port} - Upstream port
    • {http.reverse_proxy.upstream.requests} - Current request count
    • {http.reverse_proxy.upstream.latency} - Response latency
    • {http.reverse_proxy.duration} - Total proxy duration
    • {http.reverse_proxy.retries} - Number of retries performed

    Build docs developers (and LLMs) love