iducationducation
IndexArticlesFormationsProfilOutilsBibliothech
N°014 — 2026
Navigation
01Index02Articles03Formations04Profil05Outils06Bibliothech
N°014 — 2026
iducationducation
IndexArticlesFormationsProfilOutilsBibliothech
N°014 — 2026
Navigation
01Index02Articles03Formations04Profil05Outils06Bibliothech
N°014 — 2026
Formations
HCL / JSON / Bash · Avancé

Caddy

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.

Caddy 2CaddyfileAdmin APIACMEDocker
01Pourquoi Caddy — ce qu'Apache et Nginx ne font pas02Architecture interne — Apps, modules et config JSON03Caddyfile avancé — matchers, snippets, directives04Admin API — configuration dynamique en JSON05HTTPS et PKI — ACME, wildcards, on-demand TLS, CA interne06Reverse proxy avancé — load balancing, health checks, transforms07Modules et plugins — forward_auth, security, ratelimit, modules custom08Production — logs, métriques Prometheus, clustering, Docker, Kubernetes
Chapitre 8·45 min

Production — logs, métriques Prometheus, clustering, Docker, Kubernetes

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.

Logs structurés en JSON

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
}

Métriques Prometheus

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 config Prometheus

prometheus.yml
scrape_configs:
  - job_name: caddy
    static_configs:
      - targets: ['caddy:9090']
    relabel_configs:
      - source_labels: [__address__]
        target_label: instance

Dashboard Grafana

Import 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_total

Clustering — plusieurs instances Caddy

Plusieurs instances Caddy partagent les certificats via un storage backend commun.

Storage Redis partagé

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"]
    }
  }
}

Coordination ACME en cluster

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.

HAProxy devant les instances Caddy

Client → HAProxy (TCP passthrough :443) → Caddy 1
                                         → Caddy 2
                                         → Caddy 3

En TCP passthrough (mode SNI routing), les certificats restent côté Caddy et HAProxy ne voit pas le trafic décrypté.

Docker

Dockerfile production

Dockerfile
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/Caddyfile

Docker Compose production complète

compose.yml
services:
  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

Recharger la config sans downtime dans Docker

# 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 @Caddyfile

Kubernetes

Deployment standard

caddy-deployment.yaml
apiVersion: 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
    }

Service + Ingress

caddy-service.yaml
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

PKI interne Kubernetes avec Caddy

caddy-pki-secret.yaml
# 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>

Checklist production

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/WebSocket

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

Précédent
Modules et plugins — forward_auth, security, ratelimit, modules custom

Développeur fullstack passionné. J'apprends en construisant et je documente tout — front, back, outils. Le code s'apprend mieux en public.

Naviguer

IndexTous les articlesFormationsProfilOutilsBibliothech

Ailleurs

GitHub RSS

Newsletter

Les articles, libs et découvertes. Une fois par semaine, pas plus.

© 2026 William LoreeConçu & codé à la main