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 3·30 min

Caddyfile avancé — matchers, snippets, directives

Le Caddyfile de base est documenté partout. Ce chapitre couvre les fonctionnalités qui font la différence en production : matchers nommés, snippets réutilisables, import, expressions CEL, et les patterns de config que vous allez copier-coller dans vos Caddyfiles.

Options globales

Le bloc { } en tête de Caddyfile configure l'instance entière :

{
    # Admin API
    admin localhost:2019
 
    # Email pour ACME (obligatoire en prod)
    email admin@example.com
 
    # Type de clé TLS — p256 plus rapide, rsa4096 plus compatible
    key_type p256
 
    # CA ACME par défaut (Let's Encrypt staging pour les tests)
    acme_ca https://acme-staging-v02.api.letsencrypt.org/directory
 
    # Désactiver le rate limiting ACME par IP (pour les clusters)
    acme_ca_root /etc/caddy/acme-root.pem
 
    # OCSP stapling
    ocsp_stapling off  # désactiver si réseau interne sans accès OCSP
 
    # Log global
    log {
        output file /var/log/caddy/access.log {
            roll_size 100mb
            roll_keep 10
            roll_keep_for 720h
        }
        format json
        level INFO
    }
 
    # Storage backend pour les certificats
    storage file_system {
        root /var/lib/caddy
    }
 
    # Répertoire des snippets à importer
    # (via import ./snippets/*.caddy dans les blocs)
}

Matchers nommés

Les matchers nommés permettent de réutiliser des conditions complexes :

example.com {
    # Définir des matchers nommés avec @
    @api {
        path /api/*
        method GET POST PUT DELETE PATCH
    }
 
    @static {
        path /static/* /assets/* /favicon.ico
        not {
            path /static/private/*
        }
    }
 
    @authenticated {
        header Authorization "Bearer *"
    }
 
    @admin {
        remote_ip 10.0.0.0/8 192.168.0.0/16
        path /admin/*
    }
 
    @bot {
        header_regexp User-Agent `(?i)(bot|crawler|spider|scraper)`
    }
 
    # Utiliser les matchers
    handle @static {
        file_server {
            root /var/www/static
        }
    }
 
    handle @api {
        reverse_proxy localhost:3000
    }
 
    handle @admin {
        basic_auth {
            admin $2a$14$... # bcrypt hash
        }
        reverse_proxy localhost:4000
    }
 
    handle @bot {
        respond "Go away" 403
    }
 
    # Fallback
    handle {
        reverse_proxy localhost:3000
    }
}

Matchers par expression CEL

Pour les conditions complexes, Caddy supporte Common Expression Language :

@complex_rule expression `{header.X-Forwarded-For}.matches("^10\\.") && {path}.startsWith("/api/")`
 
@jwt_valid expression `{vars.jwt_claims}.contains("admin")`
 
@ratelimit_exempt expression `{header.X-API-Key} in ["key1", "key2", "key3"]`

Snippets — réutiliser la configuration

# Déclarer un snippet
(common_headers) {
    header {
        Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
        X-Content-Type-Options nosniff
        X-Frame-Options DENY
        Referrer-Policy strict-origin-when-cross-origin
        Permissions-Policy "camera=(), microphone=(), geolocation=()"
        -Server  # supprimer l'en-tête Server
    }
}
 
(cors_headers) {
    @cors_preflight method OPTIONS
    handle @cors_preflight {
        header {
            Access-Control-Allow-Origin "{header.Origin}"
            Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"
            Access-Control-Allow-Headers "Content-Type, Authorization, X-Requested-With"
            Access-Control-Max-Age "86400"
        }
        respond "" 204
    }
    header {
        Access-Control-Allow-Origin "{header.Origin}"
        Access-Control-Allow-Credentials "true"
    }
}
 
(proxy_headers) {
    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_down -X-Powered-By
    header_down -Server
}
 
(health_check) {
    lb_policy round_robin
    health_uri /health
    health_interval 10s
    health_timeout 3s
    health_status 2xx
}
 
# Utiliser les snippets
api.example.com {
    import common_headers
    import cors_headers
 
    reverse_proxy localhost:3000 localhost:3001 localhost:3002 {
        import proxy_headers
        import health_check
    }
}

Import de fichiers externes

# Importer un fichier entier
import /etc/caddy/sites/*.caddy
 
# Importer avec substitution de paramètres
(proxy_backend) {
    reverse_proxy {args[0]} {
        health_uri /health
        header_up Host {upstream_hostport}
    }
}
 
api.example.com {
    handle /v1/* {
        import proxy_backend "localhost:3001 localhost:3002"
    }
    handle /v2/* {
        import proxy_backend "localhost:4001 localhost:4002"
    }
}

Variables et placeholders

Caddy expose des placeholders {...} pour accéder au contexte de la requête :

example.com {
    log {
        output file /var/log/caddy/access.log
        format json {
            time_format rfc3339
            message_key msg
            fields {
                request>method {method}
                request>uri {uri}
                request>host {host}
                request>remote_ip {remote_host}
                request>proto {proto}
                request>headers>user-agent {request>headers>User-Agent}
                response>status {status}
                response>size {size}
                duration {duration}
            }
        }
    }
 
    # Réécrire l'URI avant proxy
    rewrite * /internal{uri}
 
    # Réponse dynamique avec placeholder
    respond "Hello {query.name}, your IP is {remote_host}" 200
}

Placeholders disponibles (sélection) :

{method}             GET, POST, ...
{uri}                /path?query
{path}               /path
{query}              key=value&...
{query.key}          valeur d'un paramètre spécifique
{host}               example.com
{scheme}             http ou https
{remote_host}        IP du client
{remote_port}        port source
{status}             code de réponse
{size}               taille de la réponse
{duration}           durée en secondes
{header.Name}        valeur d'un header de requête
{resp_header.Name}   valeur d'un header de réponse
{vars.key}           variables définies par set_var
{upstream_hostport}  hôte:port de l'upstream proxy

Patterns de production

Multi-tenant avec wildcard

*.example.com {
    tls {
        dns cloudflare {env.CF_API_TOKEN}
    }
 
    @tenant1 host tenant1.example.com
    @tenant2 host tenant2.example.com
 
    handle @tenant1 {
        reverse_proxy tenant1-app:3000
    }
 
    handle @tenant2 {
        reverse_proxy tenant2-app:3000
    }
 
    handle {
        respond "Unknown tenant" 404
    }
}

Rate limiting par route

api.example.com {
    # Module caddy-ratelimit (xcaddy)
    @expensive_routes path /api/export/* /api/report/*
 
    handle @expensive_routes {
        rate_limit {
            zone expensive {
                key {remote_host}
                events 5
                window 1m
            }
        }
        reverse_proxy localhost:3000
    }
 
    handle {
        rate_limit {
            zone default {
                key {remote_host}
                events 100
                window 1m
            }
        }
        reverse_proxy localhost:3000
    }
}

Maintenance gracieuse

example.com {
    @maintenance file {
        root /var/www
        try_files /maintenance.html
    }
 
    handle @maintenance {
        header Cache-Control "no-store"
        file_server {
            root /var/www
        }
    }
 
    handle {
        reverse_proxy localhost:3000
    }
}

Activer la maintenance : touch /var/www/maintenance.html. Désactiver : rm /var/www/maintenance.html. Zéro reload Caddy.

basic_auth — authentification HTTP native

basic_auth est intégré dans Caddy — aucun module externe. Il protège des routes avec un username/password via HTTP Basic Authentication.

Générer un hash bcrypt

# Via Caddy directement
caddy hash-password --plaintext "monmotdepasse"
# $2a$14$...hash...
 
# Via htpasswd (Apache utils)
htpasswd -nB admin
# admin:$2y$05$...

Configuration

admin.example.com {
    basic_auth {
        # username   bcrypt hash
        admin   $2a$14$hObMVdOl2LOb/djsGVIhu.dh9OHkPt5y9O1GJ4JLLGP9NN6/6TSe
        deploy  $2a$14$Qq5mRr5qYH.xK2rTEJkXTeOLCr.EFn77Yj1Lr4ZL6VbIMqhlPsra
    }
    reverse_proxy localhost:4000
}

Protéger uniquement certaines routes

example.com {
    # /admin/* protégé, reste public
    handle /admin/* {
        basic_auth {
            admin $2a$14$hObMVdOl2LOb/djsGVIhu.dh9OHkPt5y9O1GJ4JLLGP9NN6/6TSe
        }
        reverse_proxy localhost:4000
    }
 
    handle {
        reverse_proxy localhost:3000
    }
}

Avec matcher nommé

example.com {
    @protected {
        path /admin/* /api/internal/* /metrics
        not {
            remote_ip 10.0.0.0/8  # IP interne exemptée
        }
    }
 
    handle @protected {
        basic_auth {
            admin $2a$14$hObMVdOl2LOb/djsGVIhu.dh9OHkPt5y9O1GJ4JLLGP9NN6/6TSe
        }
        reverse_proxy localhost:4000
    }
 
    handle {
        reverse_proxy localhost:3000
    }
}

En JSON

{
  "handler": "authentication",
  "providers": {
    "http_basic": {
      "realm": "Admin Area",
      "accounts": [
        {
          "username": "admin",
          "password": "$2a$14$hObMVdOl2LOb/djsGVIhu.dh9OHkPt5y9O1GJ4JLLGP9NN6/6TSe"
        }
      ],
      "hash": {"algorithm": "bcrypt"}
    }
  }
}

Variables disponibles après authentification

Une fois authentifié, Caddy expose le username via {http.auth.user.id} :

example.com {
    basic_auth {
        admin $2a$14$...
    }
 
    header X-Authenticated-User {http.auth.user.id}
    reverse_proxy localhost:3000
}

basic_auth convient pour les outils internes, dashboards, endpoints de monitoring. Pour de l'authentification utilisateur en production, préférer forward_auth (chapitre 7) avec Authelia ou Authentik.


Le Caddyfile avancé est maîtrisé. Le chapitre suivant couvre l'Admin API en détail — lecture, modification, et remplacement de config en temps réel via REST.

Précédent
Architecture interne — Apps, modules et config JSON
Suivant
Admin API — configuration dynamique en JSON

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