Nginx, Caddy ou Traefik : quel reverse proxy choisir ?
Comparez Nginx, Caddy et Traefik sur un seul VPS : certificats TLS, coût de configuration par application, WebSockets et routage Docker avec une seule IP publique.
Nginx ou Caddy ou Traefik : réponse courte
Nginx, Caddy et Traefik remplissent tous les trois le même rôle de reverse proxy : écouter sur le port 443, lire le nom d’hôte de chaque requête et la transmettre au bon service sur votre VPS. N’importe lequel des trois permet de placer quatre applications auto-hébergées derrière une seule adresse IP publique. Ils sont tous suffisamment rapides pour que vos applications constituent le principal goulot d’étranglement. La différence concerne la façon dont chacun obtient un certificat TLS (transport layer security) et la quantité de configuration nécessaire pour chaque application supplémentaire. L’autre différence apparaît plus tard, le jour où vous avez besoin d’une fonction que les tutoriels courants ne couvrent pas.
Choisissez Caddy si vous voulez qu’il gère automatiquement HTTPS et que vos services soient des applications web classiques. Choisissez Traefik si tout fonctionne avec Docker Compose et que vous ajoutez un nouveau service toutes les quelques semaines. Choisissez Nginx si vous l’utilisez déjà, ou si vous avez besoin de la mise en cache des réponses, de certificats client, de la transmission TCP brute ou d’une configuration existante volumineuse que vous préférez ne pas réécrire.
Comment chacun obtient-il un certificat TLS ?
Ce critère décide du choix de la plupart des utilisateurs. Commencez donc par là. Les trois solutions finissent par utiliser le même certificat délivré par la même autorité. Les opérations nécessaires pour l’obtenir sont différentes.
Caddy demande le certificat parce que vous avez indiqué un hostname. Indiquez app.example.com comme adresse de site. Caddy demande alors un certificat via ACME (automatic certificate management environment) auprès de Let's Encrypt, utilise ZeroSSL en secours si la demande échoue, sert la redirection de HTTP vers HTTPS sur le port 80 et renouvelle automatiquement le certificat. Aucun outil supplémentaire ni timer à vérifier n’est nécessaire. Les certificats sont stockés dans le répertoire de données de l’utilisateur caddy, /var/lib/caddy/.local/share/caddy lors d’une installation par package. Ajoutez donc ce chemin à vos sauvegardes, ou acceptez une nouvelle émission après une reconstruction. Pour un hostname qui n’est pas public, Caddy signe à la place le certificat avec sa propre autorité de certification locale, tls internal. Le résultat est équivalent à créer un certificat auto-signé sur Ubuntu, mais le renouvellement est pris en charge automatiquement.
Nginx n’intègre aucun client ACME. Certbot obtient le certificat, puis son plugin --nginx réécrit votre server block pour ajouter le listener sur 443 et la redirection. Le renouvellement est exécuté par un systemd timer installé par le package. Deux éléments sont donc à vérifier : systemctl list-timers | grep certbot indique que le timer existe, et sudo certbot renew --dry-run confirme que le processus de renouvellement fonctionne toujours. La procédure détaillée se trouve dans Certbot sur Ubuntu 24.04 avec Nginx. Le même outil prend en charge un certificat wildcard avec le challenge DNS-01 lorsque vous avez plus de sous-domaines que vous ne souhaitez en énumérer.
Traefik intègre son propre client ACME. Configurez un certificate resolver dans la configuration statique. Chaque router peut ensuite l’utiliser. Tout l’état, y compris la clé de compte et les certificats, se trouve dans un unique fichier acme.json. Traefik refuse d’utiliser ce fichier s’il est lisible par un autre utilisateur que son propriétaire. Il vous en informe avant de supprimer le resolver :
The ACME resolver "le" is skipped from the resolvers list because: unable to get ACME account: permissions 660 for /letsencrypt/acme.json are too open, please use 600Montez un répertoire et laissez Traefik créer lui-même le fichier. Créez d’abord ce répertoire avec touch. Le fichier hérite ainsi de votre umask, ce qui explique pourquoi cette ligne convient à la plupart des utilisateurs.
Un point concerne les trois solutions. Le challenge HTTP-01 nécessite que le port 80 soit accessible depuis Internet, car l’autorité de certification s’y connecte. Si vous ouvrez uniquement 443, l’émission échoue d’une manière qui ressemble à un problème DNS.
Le même routage de deux applications avec trois configurations
Objectif : app.example.com doit être transmis à un service sur 127.0.0.1:8080, et files.example.com à un service sur 127.0.0.1:8081, les deux en HTTPS. Voici la configuration complète pour chaque proxy, afin de rendre visible la différence de longueur plutôt que de simplement l’affirmer.
Nginx
# /etc/nginx/sites-available/app.example.com
server {
listen 80;
server_name app.example.com;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}Liez ensuite la configuration, testez-la, rechargez Nginx, puis ajoutez le certificat.
sudo ln -s /etc/nginx/sites-available/app.example.com /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
sudo certbot --nginx -d app.example.comLa commande nginx -t qui affiche syntax is ok et test is successful est le contrôle à effectuer avant chaque rechargement. Le second bloc d’application est identique, avec le nom d’hôte et le port modifiés. Les lignes proxy_set_header ne sont pas décoratives : lorsque proxy_pass désigne une adresse, nginx envoie par défaut Host: 127.0.0.1:8080 au backend. Une application qui construit des URL absolues à partir de l’en-tête Host redirigerait sinon vos utilisateurs vers localhost.
Caddy
app.example.com {
reverse_proxy 127.0.0.1:8080
}
files.example.com {
reverse_proxy 127.0.0.1:8081
}sudo caddy validate --config /etc/caddy/Caddyfile
sudo systemctl reload caddyC’est l’intégralité du fichier. reverse_proxy configure lui-même X-Forwarded-For, X-Forwarded-Proto et X-Forwarded-Host. Par défaut, il ignore les valeurs envoyées par le client dans ces en-têtes. Une requête ne peut donc pas mentir au backend sur son origine. Les certificats, la redirection du port 80 et le renouvellement découlent tous des deux adresses des sites. Rien d’autre dans le fichier ne les demande.
Traefik
Traefik a besoin d’une configuration statique avant de pouvoir effectuer le routage. Voici un service Compose utilisant l’image correspondant à la version disponible en août 2026 :
services:
traefik:
image: traefik:v3.7
command:
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--certificatesresolvers.le.acme.email=you@example.com"
- "--certificatesresolvers.le.acme.storage=/letsencrypt/acme.json"
- "--certificatesresolvers.le.acme.httpchallenge.entrypoint=web"
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./letsencrypt:/letsencryptChaque application définit ensuite son propre routage au moyen de labels, dans son propre fichier Compose :
labels:
- "traefik.enable=true"
- "traefik.http.routers.app.rule=Host(`app.example.com`)"
- "traefik.http.routers.app.entrypoints=websecure"
- "traefik.http.routers.app.tls.certresolver=le"
- "traefik.http.services.app.loadbalancer.server.port=8080"loadbalancer.server.port est le port à l’intérieur du conteneur, et non un port publié, car Traefik accède au conteneur via un réseau Docker partagé. L’application n’a besoin d’aucune ligne ports:. C’est le véritable avantage : seul Traefik est publié. La configuration complète, avec le réseau partagé et le middleware de redirection, se trouve dans routage de plusieurs applications avec Traefik et Docker Compose.
Combien de configuration coûte chaque application supplémentaire ?
The data behind this chart
[
{
"tool": "Nginx",
"proxy_setup_lines": 0,
"lines_per_app": 11
},
{
"tool": "Caddy",
"proxy_setup_lines": 0,
"lines_per_app": 3
},
{
"tool": "Traefik",
"proxy_setup_lines": 17,
"lines_per_app": 5
}
]D’après les blocs ci-dessus. Le bloc server Nginx contient 11 lignes non vides, et vous devez le réécrire pour chaque nom d’hôte. Le bloc de site Caddy contient 3 lignes. Traefik nécessite 17 lignes de configuration statique avant de pouvoir traiter une seule requête, puis 5 labels par application.
Comparez les compromis, pas seulement le résultat final. Traefik coûte le plus avant l’ajout de la première application et le moins pour chaque application suivante. Les deux totaux se rejoignent autour du troisième site. En dessous, la configuration statique constitue une surcharge inutile. Au-delà, les labels prennent l’avantage et le conservent, car le routage se trouve à côté du service qu’il concerne. Supprimez le service et sa route disparaît avec lui. C’est précisément le point faible d’un fichier de configuration centralisé : il conserve des blocs server obsolètes pour des applications qui n’existent plus depuis des mois.
Le nombre de lignes est également favorable à Nginx. Chacun de ces blocs nécessite un lien symbolique, un nginx -t, un rechargement et une exécution de certbot, tandis que la modification de Caddy nécessite un seul rechargement et celle de Traefik aucune commande. Les trois solutions se rechargent sans interrompre les connexions actives. La différence tient au nombre d’étapes distinctes que vous devez mémoriser à une heure du matin.
Laquelle connaît vos conteneurs ?
Traefik surveille le socket Docker et construit des routeurs à partir des labels des conteneurs lorsque ceux-ci démarrent et s’arrêtent. Aucun autre outil présenté ici ne le fait. Nginx et Caddy nécessitent tous deux de modifier la configuration et de la recharger lorsqu’un nouveau conteneur apparaît. Ils ont également besoin d’une adresse qu’ils peuvent atteindre : soit un port publié sur loopback, soit un réseau Docker partagé auquel le proxy est connecté.
Cette fonctionnalité a un coût, qu’il faut présenter clairement. Traefik lit /var/run/docker.sock. Toute personne pouvant communiquer avec ce socket peut démarrer un conteneur dont le système de fichiers de l’hôte est monté à l’intérieur. Elle obtient alors root sur l’hôte. Monter le socket en lecture seule réduit le risque, sans le supprimer. Si ce point est important dans votre modèle de menace, intercalez un socket proxy qui n’expose que les endpoints de liste des conteneurs dont Traefik a besoin.
Caddy peut effectuer une découverte basée sur les labels avec un plugin développé par la communauté. Toutefois, les plugins Caddy sont intégrés à la compilation. Vous devez donc créer un binaire personnalisé ou une image personnalisée avec xcaddy, puis gérer vous-même cette version et ses mises à jour. Pour trois ou quatre services, modifier un Caddyfile demande moins de travail.
WebSockets et streaming : ce qui casse, et pourquoi
C’est Nginx qui nécessite une configuration supplémentaire. Une connexion WebSocket commence par une requête HTTP contenant Upgrade: websocket, et Nginx ne transmet pas les en-têtes hop-by-hop au backend, sauf si vous le lui indiquez.
# /etc/nginx/conf.d/upgrade-map.conf
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}Ensuite, dans le bloc location, trois lignes doivent toutes être présentes :
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;Si vous les omettez, la console du navigateur affiche WebSocket connection to 'wss://app.example.com/ws' failed, tandis que le journal du backend indique une requête GET ordinaire. La variable map est nécessaire, car une valeur Connection: upgrade codée en dur serait envoyée pour chaque requête, y compris les requêtes ordinaires qui devraient indiquer close.
Deux autres valeurs par défaut de Nginx posent problème. proxy_read_timeout vaut 60 secondes et s’applique au tunnel après la mise à niveau. Un WebSocket qui ne reçoit aucun trafic pendant une minute est donc fermé par le proxy. Les événements envoyés par le serveur arrivent aussi en retard ou par blocs tant que vous n’avez pas défini proxy_buffering off; dans cet emplacement, car Nginx conserve la réponse dans son buffer pendant que la page l’attend.
Caddy effectue la mise à niveau et transforme la connexion en tunnel bidirectionnel sans aucune directive. Il transmet également immédiatement la réponse lorsqu’elle est text/event-stream ou que sa longueur est inconnue. Le streaming fonctionne donc sans configuration supplémentaire. Traefik transmet les mises à niveau et ne met pas les réponses en buffer, sauf si vous ajoutez vous-même son middleware buffering. Si vos services comprennent un chat, un terminal web, un suivi de journaux ou des tableaux de bord en temps réel, la différence est concrète : vous écrirez et déboguerez davantage ou moins de configuration.
Le bloc serveur Nginx complet, avec WebSockets et SSE
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 80;
server_name app.example.com;
client_max_body_size 64m;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_read_timeout 3600s;
proxy_buffering off;
}
}map doit être placé dans le contexte http, et non dans server. Conservez-le donc dans son propre fichier sous /etc/nginx/conf.d/. Désactivez proxy_buffering uniquement dans les emplacements qui diffusent un flux, car le buffering permet à Nginx de libérer rapidement le worker du backend pour les réponses ordinaires. Certbot réécrit ce bloc lorsque vous l’exécutez. Relisez donc le fichier ensuite.
Que se passe-t-il lorsque vous avez besoin d’une fonction inhabituelle ?
C’est dans ce cas que Nginx justifie ses lignes de configuration supplémentaires.
- Certificats client, également appelés mTLS (TLS mutuel), lorsque le client doit lui aussi présenter un certificat. Nginx attend
ssl_client_certificate /etc/ssl/ca.pem;etssl_verify_client on;dans le bloc server. Caddy attend un blocclient_authà l’intérieur detls. Les labels Traefik ne permettent pas du tout de l’exprimer : vous définissez une option TLS dans un file provider, puis vous la référencez depuis le router avectraefik.http.routers.app.tls.options=mtls@file. Le modèle où toute la configuration tient dans les labels connaît donc une exception dès que vous avez besoin de cette fonction. - Téléversements volumineux. Nginx limite par défaut le corps des requêtes à 1 MB. Un téléversement plus volumineux renvoie
413 Request Entity Too Large, et le journal d’erreurs indiqueclient intended to send too large body. Augmentezclient_max_body_size. Caddy et Traefik n’appliquent aucune limite au corps des requêtes par défaut. La requête atteint donc votre application, qui applique sa propre limite. - Mise en cache des réponses. Nginx propose
proxy_cache, une fonction mature. Caddy nécessite un plugin compilé avec lui. La version open source de Traefik ne fournit aucun cache HTTP, ce qui surprend les utilisateurs qui pensent que tous les proxies mettent les réponses en cache. - TCP ou UDP bruts, par exemple pour le port d’une base de données ou un serveur de jeu. Nginx dispose du module
stream. Traefik propose des routers TCP et UDP sur leurs propres entrypoints. Caddy nécessite un autre plugin, et donc une autre compilation personnalisée. - Un serveur web déjà placé derrière le proxy. Si le service est une application PHP classique, une stack LAMP sur Ubuntu 24.04 inclut déjà Apache. Placer un proxy devant Apache crée alors deux endroits qui définissent les en-têtes et deux endroits qui peuvent réécrire une URL. Décidez lequel termine TLS, puis laissez l’autre en HTTP simple, lié à loopback.
Le piège du pare-feu qui suit ce choix
L’objectif d’un reverse proxy est de n’ouvrir que les ports 80 et 443. Docker contourne cette règle discrètement. La publication d’un port avec -p 8080:80 ajoute une règle DNAT dans la table nat. Cette règle est évaluée avant les règles INPUT gérées par ufw. Par conséquent, ufw deny 8080 ne bloque pas ce trafic, et votre application se retrouve exposée sur Internet, à côté du proxy que vous avez configuré avec soin. Liez les ports publiés à l’interface loopback avec 127.0.0.1:8080:80, ou supprimez complètement ports: et laissez le proxy accéder au conteneur via un réseau Docker, comme dans l’exemple Traefik ci-dessus. Le mécanisme et la correction sont expliqués dans pourquoi les ports publiés par Docker contournent ufw.
Effectuez le test depuis une machine qui n’est pas le VPS. Un test exécuté directement sur le serveur réussit toujours :
curl --max-time 5 http://your.server.address:8080Connection refused ou un timeout est le résultat attendu. Une réponse HTTP signifie que l’application est accessible sans passer par votre proxy, et que toute la configuration effectuée ci-dessus ne sert à rien.
Quel proxy choisir ?
Principalement des sites statiques, avec une ou deux applications : Caddy. HTTPS automatique supprime la principale tâche récurrente. La configuration reste suffisamment courte pour tenir sur un écran, et un site statique se résume à une ligne root et une ligne file_server dans le même bloc de site. En contrepartie, les solutions prêtes à copier-coller sont moins nombreuses lorsqu’un problème inhabituel survient.
Un homelab docker-compose auquel vous ajoutez régulièrement des services : Traefik. Au-delà du troisième service, les labels demandent moins de travail que la modification d’un fichier central, et la suppression d’un service supprime aussi sa route. Prévoyez un après-midi pour la première configuration, car les entrypoints, routers, services et middlewares constituent tous un vocabulaire nouveau. Une faute de frappe dans un label se manifeste généralement par une 404 renvoyée par Traefik plutôt que par un échec au démarrage. Consultez donc docker logs traefik pour trouver l’erreur d’analyse avant de conclure que l’application est défaillante.
Une configuration Nginx existante, ou l’une des exigences listées ci-dessus : Nginx. Il dispose déjà d’une solution pour la mise en cache des réponses et pour les certificats client, et presque tous les guides tiers partent de cette base. En contrepartie, vous devez configurer les certificats et la prise en charge des websockets au lieu de les obtenir automatiquement.
Une règle s’applique quel que soit votre choix. Un seul processus écoute sur l’interface publique, et tous les autres écoutent sur loopback ou sur un réseau Docker privé.
FAQ
Quel reverse proxy choisir pour quelques applications Docker sur un VPS ?
Pour trois ou quatre services que vous ajoutez seulement de temps en temps, Traefik est rapidement rentabilisé, car chaque application porte ses propres labels de routage et ne nécessite aucune modification d’un fichier central. Si les services sont stables et que vous voulez surtout ne plus vous occuper de HTTPS, Caddy demande moins d’apprentissage et présente moins de risques de configuration incorrecte. Choisissez Nginx si vous le connaissez déjà, ou si vous avez besoin d’une fonctionnalité absente des deux autres, comme la mise en cache des réponses ou un listener TCP brut.
Caddy n’a vraiment besoin d’aucune configuration de certificat ?
Dans le cas normal, oui. Il suffit de définir un hostname public comme adresse du site : Caddy demande le certificat via ACME, sert la redirection depuis le port 80 et renouvelle le certificat avant son expiration. Deux conditions restent nécessaires. Le port 80 doit être accessible depuis Internet pour le challenge HTTP-01, et l’enregistrement DNS A ou AAAA du hostname doit déjà pointer vers le VPS, car l’autorité de certification résout le nom et s’y connecte.
Puis-je exécuter Nginx et Traefik sur le même VPS ?
Pas sur les mêmes ports. Celui qui démarre en second ne parvient pas à effectuer le bind, et nginx affiche bind() to 0.0.0.0:443 failed (98: Address already in use), tandis que Traefik journalise une erreur de bind similaire, puis s’arrête. Exécutez un seul proxy sur 80 et 443, puis placez tout le reste derrière lui. Si vous effectuez une migration, déplacez les hostnames un par un : faites transmettre le trafic par le proxy frontal vers l’ancien proxy sur un port loopback jusqu’à ce que le dernier site ait été déplacé.
Pourquoi mes websockets se déconnectent-ils après 60 secondes derrière Nginx ?
proxy_read_timeout est défini à 60 secondes par défaut et s’applique au tunnel une fois l’upgrade terminé. Une connexion sans trafic pendant une minute est donc fermée par le proxy, et non par votre application. Augmentez cette valeur sur cette location avec proxy_read_timeout 3600s;, ou demandez à l’application d’envoyer une trame ping toutes les 30 secondes. Caddy et Traefik ne ferment pas les connexions idle ayant fait l’objet d’un upgrade après un délai fixe d’une minute. C’est pourquoi la même application peut sembler stable derrière eux et instable derrière Nginx.