Caddy de A à Z pour experts : Admin API REST, HTTPS automatique (ACME, DNS challenge, PKI interne), reverse proxy avancé, modules, on-demand TLS, clustering et production.
Le reverse_proxy de Caddy est l'un des handlers les plus riches. Ce chapitre couvre tout ce qui va au-delà du basique reverse_proxy localhost:3000 : stratégies de load balancing, health checks actifs et passifs, transforms de headers, streaming, retries intelligents.
Caddy propose plusieurs politiques de sélection d'upstream :
round_robin Distribution circulaire (défaut)
least_conn Upstream avec le moins de connexions actives
random Sélection aléatoire
random_choose N N upstreams aléatoires, puis least_conn entre eux
first Premier upstream disponible (priorité fixe)
ip_hash Hash de l'IP client → upstream fixe (sticky)
uri_hash Hash de l'URI → upstream fixe
header Hash d'un header spécifique
cookie Sticky par cookieapi.example.com {
reverse_proxy backend-1:3000 backend-2:3000 backend-3:3000 {
lb_policy least_conn
}
}{
"handler": "reverse_proxy",
"upstreams": [
{"dial": "backend-1:3000"},
{"dial": "backend-2:3000"},
{"dial": "backend-3:3000"}
],
"load_balancing": {
"selection_policy": {
"policy": "cookie",
"name": "srv",
"secret": "change-me-to-something-random-32chars"
}
}
}{
"upstreams": [
{"dial": "primary:3000", "max_requests": 100},
{"dial": "secondary:3000", "max_requests": 20}
],
"load_balancing": {
"selection_policy": {"policy": "first"}
}
}Avec first, le trafic va sur primary jusqu'à 100 connexions simultanées, puis déborde sur secondary.
Les health checks actifs sondent les upstreams périodiquement, indépendamment du trafic :
{
"handler": "reverse_proxy",
"upstreams": [
{"dial": "backend-1:3000"},
{"dial": "backend-2:3000"}
],
"health_checks": {
"active": {
"uri": "/health",
"port": 8080,
"interval": "10s",
"timeout": "3s",
"max_size": 1048576,
"expect_status": 200,
"expect_body": "ok",
"headers": {
"X-Health-Check": ["caddy"]
}
}
}
}# Vérifier l'état des upstreams en temps réel
curl -s localhost:2019/reverse_proxy/upstreams | jq .
# [
# {"address": "backend-1:3000", "healthy": true, "num_requests": 42, "fails": 0},
# {"address": "backend-2:3000", "healthy": false, "num_requests": 0, "fails": 5}
# ]Les health checks passifs observent les réponses réelles et marquent un upstream comme défaillant s'il génère trop d'erreurs :
{
"health_checks": {
"passive": {
"fail_duration": "30s",
"max_fails": 3,
"unhealthy_status": [500, 502, 503, 504],
"unhealthy_latency": "5s",
"unhealthy_request_count": 50
}
}
}Un upstream marqué défaillant reste exclu pendant fail_duration, puis est retesté.
{
"handler": "reverse_proxy",
"upstreams": [
{"dial": "backend-1:3000"},
{"dial": "backend-2:3000"}
],
"load_balancing": {
"try_duration": "10s",
"try_interval": "250ms",
"retry_match": [
{
"status_code": [502, 503, 504]
}
]
},
"health_checks": {
"passive": {
"fail_duration": "60s",
"max_fails": 5,
"unhealthy_status": [502, 503]
}
}
}try_duration : durée max pendant laquelle Caddy essaie les upstreams disponibles. Si tous échouent, retourne l'erreur au client. try_interval : délai entre chaque tentative.
reverse_proxy backend:3000 {
header_up Host {upstream_hostport}
header_up X-Real-IP {remote_host}
header_up X-Forwarded-For {remote_host}
header_up X-Forwarded-Proto {scheme}
header_up X-Forwarded-Host {host}
header_up X-Request-ID {uuid}
# Supprimer un header
header_up -Authorization
# Réécrire un header
header_up User-Agent "MyApp/1.0"
}reverse_proxy backend:3000 {
# Supprimer les headers qui révèlent la stack interne
header_down -X-Powered-By
header_down -Server
header_down -X-AspNet-Version
# Ajouter des headers de sécurité
header_down Strict-Transport-Security "max-age=31536000; includeSubDomains"
header_down X-Content-Type-Options "nosniff"
header_down X-Frame-Options "DENY"
}# Strip le préfixe /api avant de forwarder
handle /api/* {
uri strip_prefix /api
reverse_proxy backend:3000
}
# Réécrire vers un chemin différent
handle /old-path/* {
rewrite * /new-path{path}
reverse_proxy backend:3000
}Par défaut, Caddy streame les requêtes et réponses. Pour les gros uploads ou les SSE/WebSockets :
{
"handler": "reverse_proxy",
"upstreams": [{"dial": "backend:3000"}],
"request_buffers": 0,
"response_buffers": 0,
"flush_interval": -1
}flush_interval: -1 désactive le buffering de réponse — indispensable pour Server-Sent Events et les réponses de streaming LLM.
# WebSocket — Caddy gère automatiquement Upgrade
ws.example.com {
reverse_proxy backend:3000 {
header_up Host {host}
header_up Upgrade {header.Upgrade}
header_up Connection {header.Connection}
}
}Si l'upstream nécessite TLS (connexions backend-to-backend encryptées) :
{
"handler": "reverse_proxy",
"upstreams": [{"dial": "internal-service:8443"}],
"transport": {
"protocol": "http",
"tls": {
"root_ca_pool": ["/etc/caddy/internal-ca.crt"],
"client_certificate_file": "/etc/caddy/client.crt",
"client_certificate_key_file": "/etc/caddy/client.key",
"server_name": "internal-service",
"insecure_skip_verify": false
},
"dial_timeout": "5s",
"response_header_timeout": "30s",
"keep_alive": {
"enabled": true,
"probe_interval": "30s",
"max_idle_conns": 100,
"idle_conn_timeout": "90s"
}
}
}example.com {
@up_backend {
remote_ip private_ranges # requêtes internes
}
reverse_proxy backend-1:3000 backend-2:3000 {
lb_policy least_conn
health_checks {
active {
uri /health
interval 5s
timeout 2s
}
passive {
fail_duration 30s
max_fails 3
unhealthy_status 500 502 503
}
}
# Réponse si tous les upstreams sont down
handle_response {
@bad_status status 502 503
handle @bad_status {
respond "Service temporairement indisponible" 503
}
}
}
}Déployement blue/green sans coupure de service, piloté par l'API :
#!/usr/bin/env bash
# deploy-bluegreen.sh
CADDY_API="http://localhost:2019"
PROXY_ID="proxy-production"
# Déployer la nouvelle version sur le port 3001 (green)
docker run -d --name app-green -p 3001:3000 my-app:new-version
# Attendre que l'app soit prête
until curl -sf http://localhost:3001/health; do sleep 1; done
# Basculer le trafic vers green
curl -X PATCH "$CADDY_API/id/$PROXY_ID" \
-H "Content-Type: application/json" \
-d '{
"handler": "reverse_proxy",
"upstreams": [{"dial": "localhost:3001"}]
}'
echo "Traffic switched to green (port 3001)"
# Laisser quelques secondes pour que les connexions actives se terminent
sleep 10
# Arrêter le bleu
docker stop app-blue && docker rm app-blue
docker rename app-green app-blueLe reverse proxy avancé est couvert. Prochain chapitre : modules et plugins — forward_auth, caddy-security, caddy-ratelimit, et écrire un module custom avec xcaddy.