
Caddy est un serveur web moderne avec HTTPS automatique par défaut. Contrairement à Nginx ou Apache, Caddy obtient et renouvelle vos certificats TLS sans configuration. En 5 minutes, vous pouvez servir un site statique sécurisé ou proxyfier une API.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »Niveau : Débutant → Intermédiaire · Durée : 25-35 min
À la fin de ce guide, vous saurez :
- Servir un site statique avec HTTPS automatique
- Configurer un reverse proxy vers une application
- Comprendre le modèle mental du Caddyfile
- Déboguer les erreurs courantes (ACME, 502)
- Utiliser
tls internalpour les environnements LAN
Dans quel contexte ?
Section intitulée « Dans quel contexte ? »Caddy s'impose dès que vous avez besoin d'un serveur web simple et sécurisé sans passer des heures sur la configuration TLS. Voici les situations où il fait la différence :
- Exposer une API ou un dashboard derrière HTTPS sans toucher à certbot ni cron de renouvellement.
- Servir un site statique (documentation, landing page, blog Hugo/Astro) avec un Caddyfile de 3 lignes.
- Proxyfier plusieurs applications sur un même serveur (Node, Python, Go) avec routage par domaine ou chemin.
- Prototyper rapidement un environnement de dev avec
tls internalet des certificats auto-signés. - Remplacer Nginx sur un petit serveur ou un homelab sans sacrifier les performances.
- Ajouter des headers de sécurité (HSTS, CSP, X-Frame-Options) en quelques lignes.
Choisissez votre parcours
Section intitulée « Choisissez votre parcours »Le guide couvre quatre besoins distincts, et chaque lien ci-dessous ouvre directement la section correspondante. Si vous découvrez l'outil, gardez l'ordre : le site statique valide que Caddy lit bien votre configuration, le reverse proxy enchaîne sur un backend réel. Les lecteurs qui butent déjà sur un certificat ACME ou sur un 502 peuvent aller droit au dépannage.
Pourquoi Caddy plutôt que Nginx ?
Section intitulée « Pourquoi Caddy plutôt que Nginx ? »Les deux serveurs traitent le même trafic HTTP ; la différence porte sur ce que vous devez écrire pour y arriver. Caddy embarque un client ACME dans son binaire, là où Nginx délègue l'obtention et le renouvellement des certificats à certbot et à une tâche planifiée. Le tableau compare les points où ce choix d'architecture se voit concrètement, du volume de configuration au support HTTP/3.
| Critère | Caddy | Nginx |
|---|---|---|
| HTTPS | Automatique (Let's Encrypt intégré) | Manuel (certbot séparé) |
| Configuration | Lisible (Caddyfile) | Verbeux (nginx.conf) |
| Rechargement | caddy reload sans perte | nginx -s reload |
| HTTP/3 | Natif | Module externe |
| Courbe d'apprentissage | 30 min | 2-3 heures |
En résumé : Caddy pour la simplicité, Nginx pour le contrôle fin et l'écosystème mature.
Pour la culture : historique de Caddy
Caddy a été créé par Matt Holt en 2015 avec une vision claire : simplifier la configuration des serveurs web et rendre HTTPS accessible à tous. À l'époque, obtenir un certificat SSL était complexe et coûteux. L'intégration native de Let's Encrypt dans Caddy a démocratisé HTTPS.
Aujourd'hui, Caddy est utilisé en production par de nombreuses entreprises pour sa simplicité et sa robustesse. Il est écrit en Go, ce qui lui confère d'excellentes performances et une facilité de déploiement (un seul binaire).
Le modèle mental du Caddyfile
Section intitulée « Le modèle mental du Caddyfile »Avant de plonger dans la configuration, prenons 2 minutes pour comprendre comment Caddy pense. Cette compréhension vous évitera 90% des erreurs.
Visualisation
Section intitulée « Visualisation »Le schéma reprend le trajet d'une requête entrante. Le nom de domaine sélectionne le site block, les matchers filtrent ensuite selon le chemin ou la méthode, et un handler produit enfin la réponse. La couche TLS intervient en amont de tout cela et ne se déclare nulle part dans le Caddyfile.
Les 4 couches du Caddyfile
Section intitulée « Les 4 couches du Caddyfile »Pourquoi 4 couches ? Chaque requête traverse ces étapes dans l'ordre. Si vous comprenez ce flux, vous comprenez 90% de Caddy.
| Couche | Rôle | Exemples |
|---|---|---|
| Site block | Définit le domaine ou l'adresse | example.com { }, :8080 { }, localhost { } |
| Matchers | Filtrent les requêtes (chemin, méthode, headers) | /api/*, @websocket, path /static/* |
| Handlers | Traitent la requête | file_server, reverse_proxy, respond, redir |
| Réponse | HTTPS activé par défaut si domaine public | TLS auto via Let's Encrypt |
Installation
Section intitulée « Installation »Caddy se distribue sous forme d'un binaire Go unique, sans dépendance système à résoudre. Le dépôt officiel ajoute par-dessus l'unit systemd, l'utilisateur de service caddy et les répertoires de données : c'est la méthode à privilégier sur un serveur. L'image Docker convient pour un essai jetable, à condition de monter un volume sur /data, sinon les certificats obtenus sont perdus à chaque recréation du conteneur.
# Ajouter le dépôt officielsudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curlcurl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' \ | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpgcurl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' \ | sudo tee /etc/apt/sources.list.d/caddy-stable.list
# Installersudo apt update && sudo apt install caddy
# Le service systemd est configuré automatiquementsudo systemctl status caddy# Ajouter le dépôt officielsudo dnf install 'dnf-command(copr)'sudo dnf copr enable @caddy/caddysudo dnf install caddy
# Démarrer le servicesudo systemctl enable --now caddybrew install caddydocker run -d -p 80:80 -p 443:443 \ -v /path/to/Caddyfile:/etc/caddy/Caddyfile \ -v caddy_data:/data \ -v caddy_config:/config \ caddy:latestVérification :
caddy version# v2.11.4
# Le Caddyfile par défaut est dans /etc/caddy/Caddyfilecat /etc/caddy/CaddyfileServir un site statique
Section intitulée « Servir un site statique »Le premier essai se fait volontairement sur le port 8080 en HTTP, sans nom de domaine : c'est le seul moyen de vérifier que Caddy lit bien votre Caddyfile avant d'ajouter la variable TLS à l'équation. Les fichiers doivent être lisibles par l'utilisateur caddy, faute de quoi le serveur répond 403 alors que la configuration est correcte.
-
Créer le répertoire et le fichier
Fenêtre de terminal sudo mkdir -p /var/www/monsiteecho '<h1>Hello Caddy!</h1>' | sudo tee /var/www/monsite/index.htmlsudo chown -R caddy:caddy /var/www/monsite -
Configurer le Caddyfile
Fenêtre de terminal sudo tee /etc/caddy/Caddyfile << 'EOF'# Pour un test local (pas de TLS):8080 {root * /var/www/monsitefile_server}EOF -
Recharger Caddy
Fenêtre de terminal sudo systemctl reload caddy -
Tester
Fenêtre de terminal curl http://localhost:8080# <h1>Hello Caddy!</h1>
Avec un domaine public (HTTPS automatique)
Section intitulée « Avec un domaine public (HTTPS automatique) »Remplacer :8080 par un nom de domaine change le comportement du bloc entier. Caddy reconnaît un domaine public, déclenche la validation ACME auprès de Let's Encrypt et bascule son écoute sur les ports 80 et 443.
Si votre domaine pointe vers votre serveur :
monsite.com { root * /var/www/monsite file_server}C'est tout. Caddy :
- Obtient automatiquement un certificat Let's Encrypt
- Redirige HTTP → HTTPS
- Renouvelle le certificat avant expiration
SPA (Single Page Application)
Section intitulée « SPA (Single Page Application) »Une application React, Vue ou Angular gère son routage dans le navigateur : le serveur reçoit une requête sur /dashboard alors qu'aucun fichier de ce nom n'existe sur le disque. La directive try_files teste d'abord le chemin réel, puis retombe sur index.html, ce qui laisse le routeur JavaScript afficher la bonne vue au lieu d'un 404.
Pour React, Vue, Angular, etc., fallback vers index.html :
monsite.com { root * /var/www/spa try_files {path} /index.html file_server}Configurer un reverse proxy
Section intitulée « Configurer un reverse proxy »Cette partie fait passer Caddy du service de fichiers au relais HTTP devant une application qui écoute en local. Le montage est toujours le même : le backend reste sur un port non privilégié comme 3000, Caddy occupe les ports 80 et 443, termine le TLS et transmet la requête en ajoutant les en-têtes qui décrivent le client d'origine.
Qu'est-ce qu'un reverse proxy ?
Section intitulée « Qu'est-ce qu'un reverse proxy ? »Un reverse proxy se place devant vos applications et reçoit toutes les requêtes à leur place. Le client ne connaît que lui : il ignore combien de services tournent derrière, sur quels ports et sur quelles machines.
- Le backend n'est jamais joignable directement, il n'écoute que sur une interface locale
- Caddy choisit le service de destination d'après le nom de domaine ou le chemin demandé
- Il enrichit la requête d'en-têtes que le backend ne pourrait pas déduire seul, dont
X-Forwarded-Forqui porte l'adresse réelle du client
Pourquoi utiliser un reverse proxy ?
| Sans reverse proxy | Avec reverse proxy (Caddy) |
|---|---|
| Votre app Node/Python écoute sur le port 80 | Caddy écoute sur 80/443, votre app sur 3000 |
| Pas de HTTPS (ou config manuelle) | HTTPS automatique |
| Une seule app par serveur | Plusieurs apps sur le même serveur |
| Votre app gère les logs HTTP | Caddy centralise les logs |
Configuration de base
Section intitulée « Configuration de base »Une seule directive suffit quand il n'y a qu'un backend à desservir. Caddy ouvre une connexion vers localhost:3000 pour chaque requête entrante et relaie la réponse telle quelle, y compris les codes d'erreur produits par l'application.
api.example.com { reverse_proxy localhost:3000}C'est tout. En une ligne, Caddy :
- Écoute sur
api.example.comports 80 et 443 - Obtient un certificat HTTPS
- Transmet les requêtes à votre app sur
localhost:3000 - Ajoute automatiquement les headers
Host,X-Forwarded-For,X-Forwarded-Proto
Avec un chemin spécifique
Section intitulée « Avec un chemin spécifique »Un même bloc mélange sans problème proxy et fichiers statiques. Caddy classe les directives par spécificité : le matcher /api/* l'emporte sur le file_server qui sert de filet pour tout le reste. C'est le montage typique d'un frontend compilé servi à côté de son API.
example.com { # L'API sur /api/* reverse_proxy /api/* localhost:3000
# Le reste en statique root * /var/www/frontend file_server}WebSocket
Section intitulée « WebSocket »Une connexion WebSocket démarre par une requête HTTP ordinaire portant l'en-tête Upgrade. Caddy détecte cet en-tête et bascule la connexion en tunnel bidirectionnel sans directive supplémentaire, là où Nginx exige de propager explicitement Upgrade et Connection dans le bloc location.
example.com { reverse_proxy /ws/* localhost:3000}Caddy gère automatiquement l'upgrade WebSocket. Aucune configuration spéciale n'est nécessaire.
Load balancing
Section intitulée « Load balancing »Plusieurs directives to transforment le proxy en répartiteur de charge. La politique round_robin distribue les requêtes à tour de rôle entre les backends déclarés, tandis que le health check interroge /health toutes les 10 secondes et retire du pool celui qui ne répond plus, sans intervention manuelle.
example.com { reverse_proxy { to backend1:3000 to backend2:3000 to backend3:3000
lb_policy round_robin health_uri /health health_interval 10s }}HTTPS automatique : ce qu'il faut savoir
Section intitulée « HTTPS automatique : ce qu'il faut savoir »L'automatisme TLS est l'argument principal de Caddy, et c'est aussi la première source de blocage au démarrage. Cette partie détaille ce que le serveur fait réellement à la première requête sur un domaine, les prérequis DNS et réseau qu'il ne peut pas contourner, puis les deux échappatoires quand ces prérequis ne sont pas réunis : tls internal et le challenge DNS.
Comment ça marche (en 30 secondes)
Section intitulée « Comment ça marche (en 30 secondes) »Quand vous écrivez example.com { } dans votre Caddyfile, Caddy :
- Détecte que c'est un domaine public (pas localhost, pas une IP)
- Contacte Let's Encrypt pour prouver que vous contrôlez le domaine
- Obtient un certificat valide 90 jours
- Configure TLS avec les bonnes options de sécurité
- Crée une redirection HTTP → HTTPS automatique
- Renouvelle le certificat avant expiration (environ 30 jours avant)
Tout cela sans une seule ligne de configuration TLS.
Les 3 modes TLS de Caddy
Section intitulée « Les 3 modes TLS de Caddy »Caddy retient le mode automatique dès qu'un nom de domaine public apparaît en tête de bloc, sans que rien ne soit écrit. Les deux autres modes se déclarent explicitement avec la directive tls et désactivent toute demande de certificat auprès de Let's Encrypt.
| Mode | Quand l'utiliser | Configuration |
|---|---|---|
| Auto (défaut) | Domaine public accessible | example.com { ... } |
| Internal | Réseau local / développement | tls internal |
| Manuel | Certificats existants | tls /path/cert.pem /path/key.pem |
tls internal pour le LAN
Section intitulée « tls internal pour le LAN »Un domaine en .local ou .lan n'est validable par aucune autorité publique : aucun serveur ACME ne peut prouver que vous le contrôlez. La directive tls internal fait signer le certificat par la CA interne de Caddy, stockée dans son répertoire de données, ce qui reste utilisable indéfiniment sur un réseau fermé.
Pour un serveur interne sans accès Internet :
intranet.local { tls internal reverse_proxy localhost:3000}Caddy génère un certificat auto-signé. Important : vous devez installer la CA Caddy dans les navigateurs/clients pour éviter les avertissements de sécurité.
# Afficher le chemin de la CAcaddy trust# Installe la CA dans le trust store système (Linux/macOS)DNS Challenge (wildcard, serveur privé)
Section intitulée « DNS Challenge (wildcard, serveur privé) »Le challenge HTTP-01 suppose que Let's Encrypt joigne votre serveur sur le port 80, ce qui devient impossible derrière un NAT et n'a jamais été prévu pour un certificat wildcard. Le challenge DNS-01 contourne le problème en déposant un enregistrement TXT dans votre zone : il faut alors recompiler le binaire avec le module de votre hébergeur DNS à l'aide de xcaddy, le paquet officiel ne l'embarquant pas.
Si les ports 80/443 ne sont pas accessibles (firewall, NAT), utilisez le DNS Challenge :
# Compiler Caddy avec le plugin DNS (ex: Cloudflare)xcaddy build --with github.com/caddy-dns/cloudflare*.example.com { tls { dns cloudflare {env.CLOUDFLARE_API_TOKEN} } reverse_proxy localhost:3000}Pages d'erreur personnalisées
Section intitulée « Pages d'erreur personnalisées »Par défaut, Caddy renvoie le code d'erreur sans corps HTML : le navigateur affiche une page vide. Le bloc handle_errors intercepte ces réponses et rejoue le traitement de votre choix. La variable {err.status_code} sépare les erreurs client des erreurs serveur, ici une page 404 servie depuis le disque d'un côté, un message 503 généré à la volée de l'autre.
example.com { root * /var/www/html file_server
handle_errors { @404 expression {err.status_code} == 404 handle @404 { root * /var/www/errors rewrite * /404.html file_server }
@5xx expression {err.status_code} >= 500 handle @5xx { respond "Service temporairement indisponible" 503 } }}Recettes courantes
Section intitulée « Recettes courantes »Chaque configuration ci-dessous est autonome : elle constitue un Caddyfile complet, à copier puis à adapter au domaine et aux chemins réels. Elles couvrent les montages qui reviennent le plus souvent, du file_server seul au strip de préfixe, en passant par les en-têtes de sécurité.
Site statique
Section intitulée « Site statique »root ne sert rien par lui-même : c'est file_server qui lit et renvoie le fichier correspondant au chemin demandé.
example.com { root * /var/www/html file_server}SPA (React/Vue/Angular)
Section intitulée « SPA (React/Vue/Angular) »Même base, avec try_files intercalé avant file_server pour renvoyer index.html sur toute route absente du disque. Sans cette ligne, un simple rafraîchissement du navigateur sur une route interne se solde par un 404.
example.com { root * /var/www/app try_files {path} /index.html file_server}Reverse proxy simple
Section intitulée « Reverse proxy simple »Caddy termine le TLS et transmet en clair vers le port local : l'application n'a aucun certificat à gérer de son côté.
api.example.com { reverse_proxy localhost:3000}Proxy + statique (API sur /api)
Section intitulée « Proxy + statique (API sur /api) »Les blocs handle s'excluent mutuellement : la première correspondance gagne, les suivantes sont ignorées pour cette requête. Le handle sans matcher joue le rôle de cas par défaut, il doit donc rester en dernière position.
example.com { handle /api/* { reverse_proxy localhost:3000 } handle { root * /var/www/frontend file_server }}Strip path prefix (/api/users → /users)
Section intitulée « Strip path prefix (/api/users → /users) »handle_path se comporte comme handle mais retire le préfixe de l'URL avant de transmettre la requête. C'est la forme à retenir quand le backend expose ses routes à la racine et ne connaît pas le préfixe /api ajouté côté public.
example.com { handle_path /api/* { reverse_proxy localhost:3000 }}TLS interne (LAN)
Section intitulée « TLS interne (LAN) »Pour un service qui n'est jamais joignable depuis Internet. Caddy signe le certificat avec sa CA locale ; tant que caddy trust n'a pas été exécuté sur les postes clients, les navigateurs affichent un avertissement de sécurité parfaitement normal.
intranet.local { tls internal reverse_proxy localhost:3000}Certificats manuels
Section intitulée « Certificats manuels »Le cas d'un certificat fourni par l'entreprise ou par une PKI interne. Caddy ne tente alors aucune demande ACME et charge simplement la paire indiquée ; l'utilisateur de service doit pouvoir lire la clé privée, qui reste en mode 0600.
example.com { tls /etc/ssl/cert.pem /etc/ssl/key.pem file_server}Redirection permanente
Section intitulée « Redirection permanente »Après un changement de nom de domaine, le placeholder {uri} reporte le chemin et la chaîne de requête sur la nouvelle adresse, au lieu de tout renvoyer vers la page d'accueil. Le mot-clé permanent produit un 301, code que les moteurs de recherche exploitent pour transférer le référencement acquis.
old.example.com { redir https://new.example.com{uri} permanent}Headers de sécurité
Section intitulée « Headers de sécurité »La directive header ajoute ces en-têtes à toutes les réponses du bloc. HSTS interdit au navigateur de retenter le HTTP sur ce domaine pendant un an, nosniff l'empêche de deviner le type d'un fichier à partir de son contenu, et DENY bloque l'inclusion du site dans une iframe tierce.
example.com { header { Strict-Transport-Security "max-age=31536000" X-Content-Type-Options "nosniff" X-Frame-Options "DENY" } file_server}Basic Auth
Section intitulée « Basic Auth »L'authentification ne s'applique qu'au matcher indiqué, ici tout ce qui commence par /admin/. Caddy ne stocke jamais le mot de passe en clair dans le Caddyfile : la valeur attendue est un hash bcrypt.
example.com { basic_auth /admin/* { admin $2a$14$hash... } file_server}La directive s'appelle basic_auth depuis Caddy v2.8 (l'ancien basicauth fonctionne encore mais émet un avertissement). Pour générer un hash bcrypt :
caddy hash-password# Entrez le mot de passe, Caddy retourne le hash bcryptMétriques Prometheus
Section intitulée « Métriques Prometheus »L'option metrics se déclare dans le bloc global, celui qui n'a pas de nom de domaine et qui s'ouvre avant tous les sites. Aucun réglage n'est ensuite nécessaire dans les blocs de site : la collecte couvre l'ensemble des serveurs déclarés.
{ servers { metrics }}
example.com { file_server}Pièges à éviter
Section intitulée « Pièges à éviter »La grande majorité des incidents Caddy vient d'une hypothèse implicite jamais vérifiée : un enregistrement DNS qui ne pointe pas là où on croit, un port filtré en amont du serveur, ou un ordre de directives qui fait capturer la requête par le mauvais handler. Le tableau associe à chaque cause le symptôme observé et la commande qui tranche en une ligne.
| Erreur | Conséquence | Solution |
|---|---|---|
| DNS ne pointe pas vers le serveur | HTTPS auto échoue (erreur ACME) | dig example.com pour vérifier |
| Ports 80/443 fermés | Let's Encrypt ne peut pas valider | Ouvrir les ports ou utiliser DNS Challenge |
Domaine local sans tls internal | Caddy tente d'obtenir un certificat | Ajouter tls internal pour .local, .lan |
Oublier handle_path pour strip | Backend reçoit /api/users au lieu de /users | Utiliser handle_path /api/* { ... } |
| Ordre des handlers incorrect | Un handler large capture tout | Placer /api/* avant file_server catch-all |
| Backend down | Caddy renvoie 502 | curl localhost:3000 pour confirmer |
| https:// dans reverse_proxy | Erreurs de connexion | Ne pas mettre https:// sauf si backend l'exige |
Dépannage
Section intitulée « Dépannage »Caddy journalise dans journald et refuse d'appliquer une configuration invalide : ces deux comportements donnent des points d'entrée fiables pour diagnostiquer. Les échecs ACME et les 502 représentent la quasi-totalité des cas réels, et chacun se confirme en une commande avant toute modification du Caddyfile.
Commandes essentielles
Section intitulée « Commandes essentielles »caddy validate analyse la syntaxe sans toucher au service en cours d'exécution : à lancer systématiquement avant un rechargement. Le reload applique ensuite la nouvelle configuration sans couper les connexions établies, contrairement à un restart qui ferme tout.
# Valider la configurationcaddy validate --config /etc/caddy/Caddyfile
# Recharger sans downtimesudo systemctl reload caddy
# Voir les logssudo journalctl -u caddy -f
# Formater le Caddyfile (indentation)caddy fmt --overwrite /etc/caddy/CaddyfileErreurs courantes
Section intitulée « Erreurs courantes »Chaque ligne part du message effectivement affiché par le serveur, pas d'une hypothèse sur la configuration. La colonne solution indique la vérification à mener avant de modifier le Caddyfile : dans la plupart des cas, le problème est ailleurs que dans la configuration.
| Erreur | Cause probable | Solution |
|---|---|---|
| ACME challenge failed | DNS incorrect ou ports fermés | Vérifier DNS + ouvrir 80/443 |
| 502 Bad Gateway | Backend ne répond pas | curl localhost:3000 pour tester |
| too many redirects | Boucle HTTP ↔ HTTPS | Vérifier la config proxy/CDN |
| permission denied | Caddy ne peut pas lire les fichiers | chown -R caddy:caddy /var/www |
| address already in use | Autre service sur le port | ss -tlnp | grep :80 |
Déboguer une erreur ACME
Section intitulée « Déboguer une erreur ACME »Un échec d'obtention de certificat laisse toujours une trace explicite dans le journal, à condition de filtrer sur acme. Trois causes reviennent : l'enregistrement DNS absent ou pointant ailleurs, le port 80 fermé côté pare-feu, et le dépassement du rate limit Let's Encrypt, ce dernier imposant d'attendre plutôt que de relancer le service.
# Voir les détails de l'erreursudo journalctl -u caddy | grep -i acme
# Erreurs typiques :# - "no valid IP addresses" → DNS pas configuré# - "connection refused" → port 80 fermé# - "rate limit" → trop de tentatives (attendre 1h)Déboguer un 502
Section intitulée « Déboguer un 502 »Un 502 signifie que Caddy n'a obtenu aucune réponse exploitable du backend, jamais que sa propre configuration est fautive. La séquence ci-dessous isole la responsabilité en trois temps : l'application répond-elle, répond-elle aussi à l'utilisateur caddy (droits, SELinux, socket), et que dit le journal au moment exact de la requête.
# 1. Le backend tourne-t-il ?curl -I http://localhost:3000
# 2. Caddy peut-il joindre le backend ?sudo -u caddy curl http://localhost:3000
# 3. Regarder les logssudo journalctl -u caddy -fObservabilité
Section intitulée « Observabilité »Deux sorties suffisent à suivre un Caddy en production : les logs d'accès au format JSON, exploitables tels quels par Loki ou Elasticsearch sans écrire de parseur, et les métriques Prometheus exposées par l'endpoint d'administration.
Logs structurés
Section intitulée « Logs structurés »Le bloc log se déclare par site, ce qui autorise un fichier distinct par domaine. roll_size et roll_keep activent la rotation interne : Caddy la gère lui-même, il n'y a ni logrotate à configurer, ni signal à envoyer au processus après rotation.
example.com { log { output file /var/log/caddy/access.log { roll_size 100mb roll_keep 5 } format json } file_server}Métriques Prometheus
Section intitulée « Métriques Prometheus »Les compteurs exposés couvrent le nombre de requêtes, leur durée et la répartition des codes de réponse, ventilés par serveur et par handler. Ils suffisent à construire un tableau de bord de latence sans instrumenter l'application elle-même.
Ajoutez dans le bloc global :
{ servers { metrics }}
example.com { file_server}Les métriques Prometheus sont exposées sur le port :2019/metrics par défaut, prêtes à être collectées.
curl localhost:2019/metricsAide-mémoire
Section intitulée « Aide-mémoire »Les tableaux ci-dessous regroupent les directives par usage. Ils servent de rappel une fois le modèle du Caddyfile acquis, pas de documentation exhaustive : chaque directive accepte des options supplémentaires détaillées dans la référence officielle.
Fichiers
Section intitulée « Fichiers »Ces directives concernent le service de contenu depuis le disque. root ne sert rien à lui seul, il fixe uniquement le répertoire de base que file_server et try_files utilisent ensuite pour résoudre les chemins.
| Directive | Description |
|---|---|
root * /path | Définir la racine des fichiers |
file_server | Servir les fichiers statiques |
file_server browse | Avec listing des répertoires |
try_files {path} /index.html | Fallback SPA |
Les trois formes de reverse_proxy diffèrent par le chemin finalement transmis au backend. Le choix entre le matcher écrit sur la même ligne et le bloc handle_path décide si le préfixe est conservé ou retiré, ce qui explique la plupart des 404 vus côté application.
| Directive | Description |
|---|---|
reverse_proxy localhost:3000 | Proxy simple |
reverse_proxy /api/* localhost:3000 | Proxy sur un chemin |
handle_path /api/* { reverse_proxy ... } | Strip le préfixe |
La directive tls ne s'écrit que pour sortir du comportement par défaut. L'adresse indiquée sert aux notifications Let's Encrypt, notamment en cas d'échec de renouvellement : c'est le seul canal d'alerte automatique si vous n'en supervisez pas l'expiration.
| Directive | Description |
|---|---|
tls email@example.com | HTTPS auto avec email |
tls internal | Certificat auto-signé (LAN) |
tls /cert.pem /key.pem | Certificats manuels |
Headers & Sécurité
Section intitulée « Headers & Sécurité »header ajoute un en-tête aux réponses, et le préfixe - en supprime un, ce qui permet de retirer la bannière Server. encode négocie la compression avec le client selon ce qu'il annonce accepter, gzip ou zstd, et laisse les contenus déjà compressés intacts.
| Directive | Description |
|---|---|
header X-Custom "value" | Ajouter un header |
header -Server | Supprimer un header |
basic_auth /admin/* { user hash } | Auth basique |
encode gzip zstd | Compression |
redir renvoie une réponse de redirection au navigateur : l'URL change dans la barre d'adresse et une seconde requête part. rewrite modifie le chemin en interne, le client n'en voit rien. Confondre les deux est la cause classique de la boucle too many redirects.
| Directive | Description |
|---|---|
redir /old /new permanent | Redirection 301 |
rewrite /old /new | Réécriture interne |
respond "OK" 200 | Réponse directe |
handle_errors { ... } | Pages d'erreur custom |
Ces sous-commandes s'exécutent indépendamment de systemd. caddy reload applique la nouvelle configuration sans fermer les connexions en cours, et conserve l'ancienne si le fichier est invalide.
| Commande | Description |
|---|---|
caddy validate --config /etc/caddy/Caddyfile | Valider la config |
caddy fmt --overwrite Caddyfile | Formater |
caddy reload | Recharger la config |
caddy hash-password | Générer un hash bcrypt |
caddy trust | Installer la CA interne |
À retenir
Section intitulée « À retenir »Les points ci-dessous concentrent ce qui sépare une configuration Caddy stable d'une configuration qui casse au premier renouvellement de certificat ou au premier déploiement du backend.
- HTTPS automatique : fonctionne si DNS + ports 80/443 accessibles
tls internal: pour les environnements LAN sans accès Internetcaddy validate: toujours valider avant de rechargerhandle_path: pour strip le préfixe URL avant le backend- Ordre des handlers : spécifique → générique
- Logs :
journalctl -u caddy -fpour déboguer
Prochaines étapes
Section intitulée « Prochaines étapes »Caddy couvre les besoins courants d'un serveur unique. Passer à Nginx apporte un contrôle plus fin sur le cache et les modules, tandis que Traefik prend le relais dès que la configuration doit être découverte depuis Docker ou Kubernetes plutôt qu'écrite dans un fichier.
Ressources
Section intitulée « Ressources »La référence des directives liste les options que cet aide-mémoire laisse de côté, et la page HTTPS automatique détaille les cas de figure ACME peu courants. Le dépôt caddy-dns recense les modules disponibles pour le challenge DNS, à compiler avec xcaddy.
- Documentation officielle : caddyserver.com/docs
- Directives Caddyfile : caddyserver.com/docs/caddyfile/directives
- HTTPS automatique : caddyserver.com/docs/automatic-https
- GitHub : github.com/caddyserver/caddy
- Plugins DNS : github.com/caddy-dns
FAQ - Questions Fréquemment Posées
Section intitulée « FAQ - Questions Fréquemment Posées »Ces réponses reprennent en format court les points traités plus haut : structure du Caddyfile, fonctionnement du HTTPS automatique, mise en place d'un reverse proxy, et diagnostic des erreurs ACME ou 502. Elles servent de vérification rapide après lecture.
Définition
Caddy est un serveur web moderne créé en 2015 avec une philosophie : HTTPS automatique par défaut.Comparaison avec Nginx
| Critère | Caddy | Nginx |
|---|---|---|
| HTTPS | Automatique (Let's Encrypt intégré) | Manuel (certbot séparé) |
| Configuration | Lisible (Caddyfile) | Verbeux (nginx.conf) |
| HTTP/3 | Natif | Module externe |
| Courbe d'apprentissage | 30 min | 2-3 heures |
Quand choisir Caddy ?
- Simplicité : HTTPS sans configuration
- Petits projets : déploiement rapide
- Développement : certificats locaux faciles
Quand choisir Nginx ?
- Contrôle fin : configuration avancée
- Écosystème : plugins, documentation massive
- Performance extrême : optimisations poussées
Debian/Ubuntu
# Ajouter le dépôt officiel
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' \
| sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' \
| sudo tee /etc/apt/sources.list.d/caddy-stable.list
# Installer
sudo apt update && sudo apt install caddy
RHEL/Rocky/Fedora
sudo dnf install 'dnf-command(copr)'
sudo dnf copr enable @caddy/caddy
sudo dnf install caddy
sudo systemctl enable --now caddy
Vérification
caddy version
# v2.8.x
cat /etc/caddy/Caddyfile
Fonctionnement
Quand vous écrivezexample.com { } dans le Caddyfile :- Caddy détecte un domaine public
- Contacte Let's Encrypt (challenge HTTP)
- Prouve que vous contrôlez le domaine
- Obtient un certificat valide 90 jours
- Configure TLS avec les bonnes options
- Redirige HTTP → HTTPS automatiquement
- Renouvelle 30 jours avant expiration
Prérequis
| Condition | Pourquoi |
|---|---|
| DNS pointe vers le serveur | Let's Encrypt vérifie |
| Ports 80/443 accessibles | Challenge HTTP |
| Domaine public | Pas localhost, pas IP |
Exemple minimal
example.com {
root * /var/www/site
file_server
}
C'est tout. HTTPS activé sans configuration TLS.Structure de base
site_block {
directive1
directive2
}
Les 4 couches
| Couche | Rôle | Exemple |
|---|---|---|
| Site block | Définit le domaine | example.com { } |
| Matchers | Filtrent les requêtes | /api/*, @websocket |
| Handlers | Traitent la requête | file_server, reverse_proxy |
| TLS | HTTPS automatique | Activé par défaut |
Exemples
# Site statique
example.com {
root * /var/www/site
file_server
}
# Reverse proxy
api.example.com {
reverse_proxy localhost:3000
}
# SPA (React/Vue)
app.example.com {
root * /var/www/app
try_files {path} /index.html
file_server
}
Configuration de base
api.example.com {
reverse_proxy localhost:3000
}
En une ligne, Caddy :- Écoute sur ports 80 et 443
- Obtient un certificat HTTPS
- Transmet les requêtes à votre app
- Ajoute les headers
X-Forwarded-*
Avec un chemin spécifique
example.com {
handle /api/* {
reverse_proxy localhost:3000
}
root * /var/www/frontend
file_server
}
Strip le préfixe
# /api/users → backend reçoit /users
handle_path /api/* {
reverse_proxy localhost:3000
}
Load balancing
example.com {
reverse_proxy localhost:3001 localhost:3002 localhost:3003 {
lb_policy round_robin
}
}
Cas d'usage
tls internal est pour :- Serveurs intranet sans accès Internet
- Développement local
- Domaines .local, .lan
Configuration
intranet.local {
tls internal
root * /var/www/intranet
file_server
}
Éviter les avertissements navigateur
# Installer la CA Caddy dans le système
caddy trust
Les 3 modes TLS
| Mode | Usage | Config |
|---|---|---|
| Auto | Domaine public | Rien à faire |
| Internal | LAN/dev | tls internal |
| Manuel | Certificats existants | tls /cert.pem /key.pem |
Diagnostic
# Voir les logs détaillés
sudo journalctl -u caddy | grep -i acme
# Vérifier le DNS
dig example.com
# Tester les ports
nc -zv example.com 80
nc -zv example.com 443
Erreurs courantes
| Message | Cause | Solution |
|---|---|---|
| no valid IP addresses | DNS pas configuré | Configurer l'enregistrement A/AAAA |
| connection refused | Port 80 fermé | Ouvrir le pare-feu |
| rate limit | Trop de tentatives | Attendre 1h |
| unauthorized | Mauvaise IP | Vérifier que le DNS pointe vers ce serveur |
Pour le développement
Utiliseztls internal au lieu de Let's Encrypt :localhost {
tls internal
}
Causes et diagnostic
# 1. Le backend tourne-t-il ?
curl -I http://localhost:3000
# 2. Caddy peut-il le joindre ?
sudo -u caddy curl http://localhost:3000
# 3. Logs Caddy
sudo journalctl -u caddy -f
Erreurs courantes
| Problème | Solution |
|---|---|
| Backend arrêté | Démarrer l'application |
| Mauvais port | Vérifier le port dans le Caddyfile |
https:// dans reverse_proxy |
Enlever, garder http:// |
| Firewall local | Autoriser la connexion |
Configuration correcte
# Correct
reverse_proxy localhost:3000
# Incorrect (sauf si backend en HTTPS)
reverse_proxy https://localhost:3000
Commandes essentielles
| Commande | Action |
|---|---|
caddy validate --config /etc/caddy/Caddyfile |
Valider la config |
sudo systemctl reload caddy |
Recharger sans downtime |
caddy fmt --overwrite /etc/caddy/Caddyfile |
Formater le fichier |
Workflow sécurisé
# 1. Modifier le Caddyfile
sudo nano /etc/caddy/Caddyfile
# 2. Formater (optionnel mais recommandé)
caddy fmt --overwrite /etc/caddy/Caddyfile
# 3. Valider
caddy validate --config /etc/caddy/Caddyfile
# 4. Recharger
sudo systemctl reload caddy
Autres commandes utiles
# Générer un hash bcrypt pour basicauth
caddy hash-password
# Installer la CA interne
caddy trust
Configuration SPA
app.example.com {
root * /var/www/app
try_files {path} /index.html
file_server
}
Explication
| Directive | Rôle |
|---|---|
root * /var/www/app |
Dossier racine |
try_files {path} /index.html |
Si fichier absent → index.html |
file_server |
Servir les fichiers statiques |
Pourquoi try_files ?
Dans une SPA :/aboutn'existe pas comme fichier- Le routeur frontend (React Router, Vue Router) gère l'URL
- Il faut donc renvoyer
index.htmlpour toutes les routes
Avec cache
app.example.com {
root * /var/www/app
try_files {path} /index.html
file_server
header /assets/* Cache-Control "public, max-age=31536000"
}
Générer le hash
caddy hash-password
# Entrez le mot de passe
# Caddy retourne : $2a$14$...
Configuration
example.com {
basicauth /admin/* {
admin $2a$14$Zkx19XLiW6VYouLHR5NmfOFU0z2GTNmpkT/5qqR7hx4IjWJPDhjvG
}
root * /var/www/site
file_server
}
Plusieurs utilisateurs
basicauth /admin/* {
alice $2a$14$hash1...
bob $2a$14$hash2...
}
Points importants
- Le hash doit être bcrypt (pas MD5, pas SHA)
- Le chemin
/admin/*inclut le slash final - Combinez avec HTTPS (déjà activé par défaut)
Configuration
{
servers {
metrics
}
}
example.com {
root * /var/www/site
file_server
}
Accès aux métriques
curl localhost:2019/metrics
Métriques disponibles
| Métrique | Description |
|---|---|
caddy_http_requests_total |
Total requêtes |
caddy_http_request_duration_seconds |
Latence |
caddy_http_response_size_bytes |
Taille réponses |
Intégration Prometheus
# prometheus.yml
scrape_configs:
- job_name: 'caddy'
static_configs:
- targets: ['caddy-server:2019']