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
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
}
}
}
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.
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