Skip to main content
The reverse proxy module is a powerful and flexible handler that proxies HTTP requests to upstream servers with load balancing, health checks, circuit breakers, and more. Module ID: http.handlers.reverse_proxy

Overview

Upon proxying, the reverse proxy sets placeholders that can be used in subsequent handlers or logging:
PlaceholderDescription
{http.reverse_proxy.upstream.address}Full address to the upstream as given in config
{http.reverse_proxy.upstream.hostport}Host:port of the upstream
{http.reverse_proxy.upstream.host}Host of the upstream
{http.reverse_proxy.upstream.port}Port of the upstream
{http.reverse_proxy.upstream.requests}Approximate current number of requests to the upstream
{http.reverse_proxy.upstream.max_requests}Maximum approximate number of requests allowed
{http.reverse_proxy.upstream.fails}Number of recent failed requests
{http.reverse_proxy.upstream.latency}Time for upstream to write response header
{http.reverse_proxy.upstream.latency_ms}Same as latency, but in milliseconds
{http.reverse_proxy.upstream.duration}Time spent proxying, including writing response body
{http.reverse_proxy.upstream.duration_ms}Same as duration, but in milliseconds
{http.reverse_proxy.duration}Total time spent proxying, including retries
{http.reverse_proxy.duration_ms}Same as duration, but in milliseconds
{http.reverse_proxy.retries}Number of retries actually performed

Basic Configuration

upstreams
array
Static list of backends to proxy to.
dial
string
required
The address to dial to reach the upstream. Supports placeholders and network addresses.Examples:
  • localhost:8080
  • 192.168.1.10:9000
  • unix//var/run/app.sock
max_requests
integer
Maximum concurrent requests to this upstream. If exceeded, the upstream is considered unhealthy.
dynamic_upstreams
module
Module for retrieving the list of upstreams dynamically. Dynamic upstreams are retrieved at every iteration of the proxy loop for each request.
Active health checks do not work on dynamic upstreams. Passive health checks are only effective if the proxy is busy enough that concurrent requests to the same backends are continuous.
Module namespace: http.reverse_proxy.upstreams

Transport

transport
module
Configures the method of transport for the proxy. A transport performs the actual “round trip” to the backend. The default is plaintext HTTP.Module namespace: http.reverse_proxy.transportAvailable transports:
  • http - Standard HTTP transport (default)
  • fastcgi - FastCGI transport for PHP and other applications

Load Balancing

load_balancing
object
Distributes load/requests between backends.
selection_policy
module
How to select an upstream from the pool. Default is random.Available policies:
  • random - Select randomly
  • random_choose - Select N randomly, pick least loaded
  • least_conn - Pick upstream with fewest active requests
  • round_robin - Cycle through upstreams sequentially
  • weighted_round_robin - Round robin with weights
  • first - Select first available
  • ip_hash - Hash based on client IP
  • client_ip_hash - Hash based on trusted client IP
  • uri_hash - Hash based on request URI
  • query - Hash based on query parameter
  • header - Hash based on request header
  • cookie - Hash based on cookie (with sticky sessions)
try_duration
duration
How long to try selecting available backends for each request.
try_interval
duration
default:"250ms"
How long to wait between selecting the next host when retrying.
retries
integer
Maximum number of retries (not counting the first attempt).
retry_match
matcher
Request matcher for determining which requests are idempotent and safe to retry. By default, only GET requests are retried.

Health Checks

health_checks
object
Configures active and passive health checks.

Active Health Checks

active
object
Run in the background on a timer. Do not work for dynamic upstreams.
uri
string
The URI (path and query) to use for health checks.
port
integer
Alternative port to use for health checks (if different from upstream dial address).
headers
object
HTTP headers to set on health check requests.
interval
duration
default:"30s"
How frequently to perform active health checks.
timeout
duration
default:"5s"
How long to wait for a response before considering the backend unhealthy.
passes
integer
default:"1"
Number of consecutive passes before marking a previously unhealthy backend as healthy.
fails
integer
default:"1"
Number of consecutive failures before marking a previously healthy backend as unhealthy.
expect_status
integer
The HTTP status code to expect from a healthy backend.
expect_body
string
Regular expression to match against the response body of a healthy backend.

Passive Health Checks

passive
object
Monitor proxied requests for errors or timeouts. State is shared globally across all handlers.
fail_duration
duration
How long to remember a failed request. Must be > 0 to enable passive health checks.
max_fails
integer
default:"1"
Number of failures within fail_duration to mark a backend as down.
unhealthy_request_count
integer
Mark backend as down if it has this many concurrent requests or more.
unhealthy_status
array of integers
HTTP status codes that count as failures.
unhealthy_latency
duration
Count request as failed if response takes at least this long.

Circuit Breaker

circuit_breaker
module
Acts as an early-warning system for health checks when backends are getting overloaded.Module namespace: http.reverse_proxy.circuit_breakers

Headers

headers
object
Manipulates headers between Caddy and the backend.By default, all headers are passed through without changes, except for special hop-by-hop headers.X-Forwarded-For, X-Forwarded-Proto, and X-Forwarded-Host are set implicitly.
request
object
Header operations to apply to the request before sending to upstream.
response
object
Header operations to apply to the response before sending to client.
trusted_proxies
array of strings
IP ranges (CIDR notation) from which X-Forwarded-* header values should be trusted. By default, no proxies are trusted.
"trusted_proxies": ["10.0.0.0/8", "172.16.0.0/12"]

Request/Response Handling

rewrite
object
Rewrites the copy of the upstream request. Allows changing the request method and URI.The rewrite is applied to the copy, so it doesn’t persist past the reverse proxy handler.If the method is changed to GET or HEAD, the request body will not be copied to the backend.
handle_response
array
List of handlers to evaluate after successful roundtrips. The first handler that matches the response will be invoked.Three new placeholders available in this handler chain:
  • {http.reverse_proxy.status_code} - Status code from response
  • {http.reverse_proxy.status_text} - Status text from response
  • {http.reverse_proxy.header.*} - Headers from response
"handle_response": [
  {
    "match": {
      "status_code": [404]
    },
    "routes": [
      {"handle": [{"handler": "file_server"}]}
    ]
  }
]

Buffering

request_buffers
integer (bytes)
If nonzero, the entire request body up to this size will be read and buffered in memory before being proxied.
This should be avoided if possible for performance reasons, but could be useful if the backend is intolerant of read latency or chunked encodings.
response_buffers
integer (bytes)
If nonzero, the entire response body up to this size will be read and buffered in memory before being proxied to the client.
flush_interval
duration
How often to flush the response buffer. By default, no periodic flushing is done.A negative value disables response buffering and flushes immediately after each write.

Streaming

stream_timeout
duration
If nonzero, streaming requests such as WebSockets will be forcibly closed at the end of the timeout.
stream_close_delay
duration
If nonzero, streaming requests will not be closed when the proxy config is unloaded. Instead, the stream will remain open until the delay is complete.This prevents streams from closing when Caddy’s config is reloaded, avoiding a thundering herd of reconnecting clients.

Configuration Examples

Basic Reverse Proxy

{
  "handler": "reverse_proxy",
  "upstreams": [
    {"dial": "localhost:8080"}
  ]
}

Multiple Upstreams with Load Balancing

{
  "handler": "reverse_proxy",
  "upstreams": [
    {"dial": "backend1:8080"},
    {"dial": "backend2:8080"},
    {"dial": "backend3:8080"}
  ],
  "load_balancing": {
    "selection_policy": {
      "policy": "least_conn"
    }
  }
}

With Health Checks

{
  "handler": "reverse_proxy",
  "upstreams": [
    {"dial": "backend:8080"}
  ],
  "health_checks": {
    "active": {
      "uri": "/health",
      "interval": "30s",
      "timeout": "5s"
    },
    "passive": {
      "fail_duration": "30s",
      "max_fails": 3,
      "unhealthy_status": [500, 502, 503]
    }
  }
}

With Header Manipulation

{
  "handler": "reverse_proxy",
  "upstreams": [{"dial": "backend:8080"}],
  "headers": {
    "request": {
      "set": {
        "X-Real-IP": ["{http.request.remote.host}"],
        "X-Custom-Header": ["value"]
      }
    },
    "response": {
      "add": {
        "X-Served-By": ["Caddy"]
      }
    }
  }
}
The reverse proxy automatically adds X-Forwarded-For, X-Forwarded-Proto, and X-Forwarded-Host headers unless explicitly configured otherwise.

Build docs developers (and LLMs) love