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.
Ce chapitre couvre le déploiement Caddy en production : logs structurés avec rotation, métriques Prometheus, clustering multi-instances, containerisation Docker, et déploiement Kubernetes avec cert-manager ou la PKI native.
La configuration de logs la plus complète pour la production :
{
"logging": {
"logs": {
"default": {
"writer": {
"output": "file",
"filename": "/var/log/caddy/error.log",
"roll_size_mb": 100,
"roll_keep": 10,
"roll_keep_for": "720h"
},
"encoder": {"format": "json"},
"level": "WARN"
},
"access": {
"writer": {
"output": "file",
"filename": "/var/log/caddy/access.log",
"roll_size_mb": 500,
"roll_keep": 20,
"roll_keep_for": "168h"
},
"encoder": {
"format": "json",
"time_format": "unix_ms",
"fields": {
"request>headers>Authorization>0": {"filter": "replace", "value": "[REDACTED]"},
"request>headers>Cookie>0": {"filter": "replace", "value": "[REDACTED]"},
"request>headers>Set-Cookie>0": {"filter": "replace", "value": "[REDACTED]"}
}
},
"level": "INFO",
"include": ["http.log.access"]
}
}
}
}Par vhost en Caddyfile :
example.com {
log {
output file /var/log/caddy/example.com.log {
roll_size 100mb
roll_keep 5
roll_keep_for 168h
}
format json {
time_format unix_ms
}
level INFO
}
reverse_proxy backend:3000
}Caddy expose un endpoint /metrics compatible Prometheus :
{
servers {
metrics
}
}
:2019 {
metrics /metrics
}Ou en JSON pour l'exposer sur un port dédié :
{
"apps": {
"http": {
"servers": {
"metrics_server": {
"listen": ["127.0.0.1:9090"],
"routes": [{
"handle": [{
"handler": "metrics"
}]
}]
}
}
}
}
}Métriques exposées :
caddy_http_requests_total{handler, code, method}
caddy_http_request_duration_seconds{handler}
caddy_http_request_size_bytes{handler}
caddy_http_response_size_bytes{handler}
caddy_tls_handshakes_total{protocol, cipher_suite}
caddy_tls_certificates_total
caddy_reverse_proxy_upstreams_healthy{upstream}scrape_configs:
- job_name: caddy
static_configs:
- targets: ['caddy:9090']
relabel_configs:
- source_labels: [__address__]
target_label: instanceImport du dashboard officiel Caddy : ID 20802 sur grafana.com.
Panels utiles à créer manuellement :
Requêtes/s : rate(caddy_http_requests_total[1m])
Latence p99 : histogram_quantile(0.99, rate(caddy_http_request_duration_seconds_bucket[5m]))
Upstreams sains : caddy_reverse_proxy_upstreams_healthy == 1
Certificats TLS : caddy_tls_certificates_totalPlusieurs instances Caddy partagent les certificats via un storage backend commun.
xcaddy build --with github.com/pberkel/caddy-storage-redis{
"storage": {
"module": "redis",
"host": "redis-sentinel:26379",
"password": "{env.REDIS_PASSWORD}",
"db": 1,
"key_prefix": "caddy:prod:",
"tls": {
"insecure_skip_verify": false,
"root_ca_pool": ["/etc/ssl/redis-ca.crt"]
}
}
}Sans coordination, plusieurs instances peuvent déclencher simultanément le challenge ACME pour le même domaine et se bloquer mutuellement. Redis résout cela via des locks distribués — le module caddy-storage-redis les gère automatiquement.
Client → HAProxy (TCP passthrough :443) → Caddy 1
→ Caddy 2
→ Caddy 3En TCP passthrough (mode SNI routing), les certificats restent côté Caddy et HAProxy ne voit pas le trafic décrypté.
FROM caddy:2-builder AS builder
RUN xcaddy build \
--with github.com/caddy-dns/cloudflare \
--with github.com/mholt/caddy-ratelimit \
--with github.com/pberkel/caddy-storage-redis
FROM caddy:2
COPY --from=builder /usr/bin/caddy /usr/bin/caddy
COPY Caddyfile /etc/caddy/Caddyfileservices:
caddy:
build: .
restart: unless-stopped
ports:
- "80:80"
- "443:443"
- "443:443/udp" # HTTP/3 QUIC
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data
- caddy_config:/config
- /var/log/caddy:/var/log/caddy
environment:
CF_API_TOKEN: ${CF_API_TOKEN}
networks:
- proxy
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:2019/config/"]
interval: 30s
timeout: 5s
retries: 3
app:
image: my-app:latest
restart: unless-stopped
networks:
- proxy
expose:
- "3000"
redis:
image: redis:7-alpine
restart: unless-stopped
command: redis-server --requirepass ${REDIS_PASSWORD} --bind 127.0.0.1
volumes:
- redis_data:/data
networks:
- proxy
volumes:
caddy_data:
caddy_config:
redis_data:
networks:
proxy:
driver: bridge# Reload en passant par l'API (préférable à docker restart)
docker exec caddy caddy reload --config /etc/caddy/Caddyfile
# Ou via l'API directement
curl -X POST localhost:2019/load \
-H "Content-Type: text/caddyfile" \
--data-binary @CaddyfileapiVersion: apps/v1
kind: Deployment
metadata:
name: caddy
namespace: ingress
spec:
replicas: 3
selector:
matchLabels:
app: caddy
template:
metadata:
labels:
app: caddy
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "9090"
spec:
containers:
- name: caddy
image: my-registry/caddy-custom:v2.9.1
ports:
- containerPort: 80
name: http
- containerPort: 443
name: https
- containerPort: 443
protocol: UDP
name: https-quic
- containerPort: 9090
name: metrics
env:
- name: CF_API_TOKEN
valueFrom:
secretKeyRef:
name: caddy-secrets
key: cf-api-token
volumeMounts:
- name: config
mountPath: /etc/caddy
- name: caddy-data
mountPath: /data
resources:
requests:
memory: "64Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "500m"
livenessProbe:
httpGet:
path: /config/
port: 2019
initialDelaySeconds: 10
periodSeconds: 30
readinessProbe:
httpGet:
path: /config/
port: 2019
initialDelaySeconds: 5
periodSeconds: 10
volumes:
- name: config
configMap:
name: caddy-config
- name: caddy-data
persistentVolumeClaim:
claimName: caddy-data
---
apiVersion: v1
kind: ConfigMap
metadata:
name: caddy-config
namespace: ingress
data:
Caddyfile: |
{
admin 0.0.0.0:2019
storage redis {
host redis:6379
password {env.REDIS_PASSWORD}
}
servers {
metrics
}
}
:80, :443 {
tls {
dns cloudflare {env.CF_API_TOKEN}
}
reverse_proxy http://app-service:3000
}apiVersion: v1
kind: Service
metadata:
name: caddy
namespace: ingress
spec:
type: LoadBalancer
selector:
app: caddy
ports:
- name: http
port: 80
targetPort: 80
- name: https
port: 443
targetPort: 443
- name: metrics
port: 9090
targetPort: 9090
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: caddy-data
namespace: ingress
spec:
accessModes:
- ReadWriteMany # pour plusieurs réplicas
storageClassName: nfs-client # ou cephfs
resources:
requests:
storage: 5Gi# Stocker root CA et clé comme Secret K8s
apiVersion: v1
kind: Secret
metadata:
name: caddy-pki-root
namespace: ingress
type: Opaque
data:
root.crt: <base64-encoded-cert>
root.key: <base64-encoded-key>Sécurité
[ ] Admin API bind sur 127.0.0.1 uniquement (ou non exposée)
[ ] Headers Authorization/Cookie filtrés dans les logs
[ ] HSTS activé (min 1 an)
[ ] TLS 1.2 minimum (1.3 préféré)
[ ] Secrets via variables d'environnement, pas dans la config
TLS
[ ] Email ACME configuré
[ ] Storage backend partagé si cluster
[ ] Challenge DNS pour wildcards
[ ] Renouvellement testé (staging ACME d'abord)
Observabilité
[ ] Logs JSON avec rotation
[ ] Endpoint métriques exposé (pas en public)
[ ] Dashboard Grafana opérationnel
[ ] Alertes sur caddy_reverse_proxy_upstreams_healthy
Résilience
[ ] Health checks actifs + passifs configurés
[ ] try_duration + retry_match sur les upstreams
[ ] Réponse de maintenance si tous les upstreams sont down
[ ] Rolling restart testé (zéro connexion perdue)
Performance
[ ] HTTP/3 (QUIC) activé sur :443/udp
[ ] Compression (zstd + brotli + gzip) activée
[ ] keep_alive configuré sur les transports upstream
[ ] Buffers streaming désactivés si SSE/WebSocketFormation Caddy terminée. Vous avez maintenant une connaissance complète de l'architecture, du Caddyfile avancé, de l'Admin API, de TLS et PKI, du reverse proxy de production, des modules custom, et du déploiement Docker + Kubernetes.