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 système TLS de Caddy n'est pas un wrapper autour de Certbot. C'est un moteur ACME natif avec gestion de certificats, renouvellement automatique, challenges multiples, PKI interne, et on-demand TLS pour des milliers de domaines. Ce chapitre couvre tout.
Quand Caddy démarre avec example.com dans sa config, il suit ce chemin :
1. Domaine qualifié publiquement ? (pas *.local, pas IP)
2. Certificat déjà en cache et valide > 30 jours ? → utilise-le
3. Challenge ACME sélectionné (HTTP-01 par défaut, TLS-ALPN-01 fallback)
4. Obtention du certificat, stockage dans storage backend
5. OCSP stapling activé automatiquement
6. Renouvellement déclenché quand < 30 jours de validité restantsTout cela sans configuration supplémentaire. La seule chose requise : que le domaine DNS pointe vers votre serveur et que les ports 80/443 soient accessibles.
Caddy crée un fichier temporaire sur /.well-known/acme-challenge/ et le sert sur le port 80. Let's Encrypt le vérifie.
Limitations : nécessite le port 80 ouvert, ne supporte pas les wildcards.
Caddy répond au challenge via une extension TLS spéciale sur le port 443. Utile si le port 80 est bloqué.
{
"apps": {
"tls": {
"automation": {
"policies": [{
"subjects": ["example.com"],
"issuers": [{
"module": "acme",
"challenges": {
"tls_alpn": {}
}
}]
}]
}
}
}
}Le seul challenge qui supporte les wildcards (*.example.com) et les domaines non-accessibles depuis l'extérieur. Nécessite un module DNS provider.
xcaddy build \
--with github.com/caddy-dns/cloudflare \
--with github.com/caddy-dns/route53 \
--with github.com/caddy-dns/digitalocean{
email admin@example.com
}
*.example.com, example.com {
tls {
dns cloudflare {env.CF_API_TOKEN}
}
reverse_proxy localhost:3000
}Ou en JSON pour plus de contrôle :
{
"apps": {
"tls": {
"automation": {
"policies": [{
"subjects": ["*.example.com", "example.com"],
"issuers": [{
"module": "acme",
"ca": "https://acme-v02.api.letsencrypt.org/directory",
"email": "admin@example.com",
"challenges": {
"dns": {
"provider": {
"name": "cloudflare",
"api_token": "{env.CF_API_TOKEN}"
},
"resolvers": ["1.1.1.1:53", "8.8.8.8:53"],
"ttl": 120,
"propagation_timeout": 120
}
}
}]
}]
}
}
}
}Let's Encrypt est la CA par défaut. Caddy supporte ZeroSSL nativement comme fallback automatique :
{
email admin@example.com
acme_ca https://acme.zerossl.com/v2/DV90
}Ou configurer les deux CA en parallèle — Caddy essaie l'une si l'autre échoue :
{
"automation": {
"policies": [{
"subjects": ["example.com"],
"issuers": [
{
"module": "acme",
"ca": "https://acme-v02.api.letsencrypt.org/directory",
"email": "admin@example.com"
},
{
"module": "acme",
"ca": "https://acme.zerossl.com/v2/DV90",
"email": "admin@example.com"
}
]
}]
}
}On-demand TLS permet à Caddy d'obtenir des certificats pendant la première connexion TLS — pas au démarrage. Indispensable pour les plateformes multi-tenant qui hébergent des milliers de domaines clients.
{
on_demand_tls {
ask https://your-api.example.com/check-domain
interval 2m
burst 5
}
}
# Catch-all qui déclenche on-demand TLS
:443 {
tls {
on_demand
}
reverse_proxy localhost:3000
}Le endpoint ask est appelé avant chaque nouvelle obtention de certificat :
GET https://your-api.example.com/check-domain?domain=client-domaine.com
→ 200 : certificat autorisé
→ 4xx/5xx : refusImplémentation Node.js / Next.js du endpoint ask :
import { NextRequest, NextResponse } from 'next/server'
export async function GET(req: NextRequest) {
const domain = req.nextUrl.searchParams.get('domain')
if (!domain) return NextResponse.json({ error: 'no domain' }, { status: 400 })
// Vérifier que ce domaine est bien configuré dans votre DB
const isValid = await db.domains.findFirst({
where: { domain, active: true }
})
if (!isValid) return NextResponse.json({ error: 'domain not found' }, { status: 404 })
return NextResponse.json({ ok: true })
}Pour les réseaux internes, les services Kubernetes, ou le mTLS entre microservices, Caddy peut être votre propre autorité de certification.
{
"apps": {
"pki": {
"certificate_authorities": {
"local": {
"name": "My Internal CA",
"root": {
"format": "pem_file",
"certificate": "/etc/caddy/root.crt",
"private_key": "/etc/caddy/root.key"
},
"intermediate": {
"format": "pem_file",
"certificate": "/etc/caddy/intermediate.crt",
"private_key": "/etc/caddy/intermediate.key"
}
}
}
},
"tls": {
"automation": {
"policies": [{
"subjects": ["*.internal.example.com", "internal.example.com"],
"issuers": [{"module": "internal", "ca": "local"}],
"key_type": "p256"
}]
}
}
}
}# Générer une clé EC P-256
openssl ecparam -name prime256v1 -genkey -noout -out root.key
# Certificat root auto-signé (10 ans)
openssl req -new -x509 -days 3650 \
-key root.key \
-out root.crt \
-subj "/C=FR/O=My Org/CN=My Internal CA"
# Optionnel : clé intermédiaire (bonne pratique)
openssl ecparam -name prime256v1 -genkey -noout -out intermediate.key
openssl req -new -key intermediate.key -out intermediate.csr \
-subj "/C=FR/O=My Org/CN=My Internal CA Intermediate"
openssl x509 -req -in intermediate.csr \
-CA root.crt -CAkey root.key -CAcreateserial \
-days 1825 -out intermediate.crt \
-extensions v3_ca \
-extfile <(printf "[v3_ca]\nbasicConstraints=CA:TRUE\nkeyUsage=keyCertSign,cRLSign")# Linux (Debian/Ubuntu)
cp root.crt /usr/local/share/ca-certificates/my-internal-ca.crt
update-ca-certificates
# macOS
sudo security add-trusted-cert -d -r trustRoot \
-k /Library/Keychains/System.keychain root.crt
# Windows
certutil -addstore -f "ROOT" root.crt
# Via Admin API Caddy (installe dans le trust store du système)
curl -X POST localhost:2019/pki/ca/local/installAvec la PKI Caddy, vous pouvez forcer le mTLS sur des routes spécifiques :
{
"handle": [{
"handler": "authentication",
"providers": {
"mtls": {
"trusted_ca_pool": ["/etc/caddy/root.crt"],
"any_subject": true
}
}
}, {
"handler": "reverse_proxy",
"upstreams": [{"dial": "backend:8080"}]
}]
}{
"tls_connection_policies": [{
"match": {"sni": ["secure.example.com"]},
"protocol_min": "tls1.3",
"cipher_suites": [
"TLS_AES_128_GCM_SHA256",
"TLS_AES_256_GCM_SHA384",
"TLS_CHACHA20_POLY1305_SHA256"
],
"curves": ["x25519", "secp256r1"]
}]
}{
"apps": {
"tls": {
"certificates": {
"load_files": [{
"certificate": "/etc/ssl/custom/cert.pem",
"key": "/etc/ssl/custom/key.pem",
"tags": ["custom-cert"]
}]
}
}
}
}# Vérifier les certificats gérés
curl -s localhost:2019/config/apps/tls | jq '.certificates'
# Forcer le renouvellement d'un certificat
curl -X DELETE "localhost:2019/config/apps/tls/certificates/automate" \
-H "Content-Type: application/json" \
-d '["example.com"]'
curl -X POST "localhost:2019/config/apps/tls/certificates/automate" \
-H "Content-Type: application/json" \
-d '["example.com"]'
# Tester la configuration TLS
openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | \
openssl x509 -noout -dates -subject -issuer
# Logs Caddy filtrés sur TLS
journalctl -u caddy -f | grep -i "tls\|cert\|acme"La stack TLS est maîtrisée. Chapitre suivant : reverse proxy avancé — load balancing, health checks actifs/passifs, transforms de headers, retries et circuit breaker.