Nginx, Caddy ou Traefik : quel 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 IP publique.
Nginx vs Caddy vs Traefik : réponse courte
Nginx, Caddy et Traefik remplissent tous le même rôle de reverse proxy : ils écoutent sur le port 443, lisent le nom d’hôte de chaque requête et la transmettent au bon service sur votre VPS. N’importe lequel des trois peut placer quatre applications auto-hébergées derrière une seule adresse IP publique, et tous sont suffisamment rapides pour que vos applications constituent le facteur limitant. La différence tient à la manière dont chacun obtient un certificat TLS (transport layer security) et au niveau de configuration que vous demande chaque application supplémentaire. L’autre différence apparaît plus tard, le jour où vous avez besoin de quelque chose que les tutoriels courants ne couvrent pas.
Choisissez Caddy si vous voulez qu’HTTPS soit géré automatiquement 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 mise en cache des réponses, de certificats client, de la redirection 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 est déterminant pour 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 étapes nécessaires pour l’obtenir sont toutefois différentes.
Caddy demande le certificat parce que vous avez indiqué un nom d’hôte. Indiquez app.example.com comme adresse de site. Caddy demande alors un certificat via ACME (environnement de gestion automatique des certificats) auprès de Let's Encrypt, puis utilise ZeroSSL en cas d’échec. Il sert la redirection HTTP vers HTTPS sur le port 80 et renouvelle automatiquement le certificat. Vous n’avez besoin ni d’un outil supplémentaire ni d’un timer à vérifier. Les certificats sont stockés dans le répertoire de données de l’utilisateur caddy, /var/lib/caddy/.local/share/caddy avec une installation par paquet. Ajoutez donc ce chemin à vos sauvegardes ou acceptez une nouvelle émission après une reconstruction. Pour un nom d’hôte qui n’est pas public, tls internal signe le certificat avec sa propre autorité de certification locale. Vous obtenez alors la même chose qu’en créant un certificat autosigné sur Ubuntu, avec le renouvellement géré automatiquement.
Nginx n’intègre aucun client ACME. Certbot obtient le certificat, et son plugin --nginx réécrit votre server block pour ajouter le listener 443 et la redirection. Le renouvellement s’exécute à l’aide d’un systemd timer installé par le paquet. Il y a donc deux éléments à surveiller et deux vérifications à effectuer : systemctl list-timers | grep certbot confirme que le timer existe, et sudo certbot renew --dry-run vérifie que le renouvellement fonctionne toujours. La procédure détaillée se trouve dans Certbot sur Ubuntu 24.04 avec Nginx. Le même outil permet également d’obtenir un certificat wildcard avec le challenge DNS-01 lorsque vous avez plus de sous-domaines que vous ne souhaitez en déclarer.
Traefik intègre son propre client ACME. Configurez un certificate resolver dans la configuration statique. Chaque router pourra ensuite l’utiliser. Tout l’état, y compris la clé de compte et les certificats, est stocké dans un seul 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 désactiver 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. Il héritera ainsi de votre umask, ce qui explique pourquoi la plupart des utilisateurs voient cette ligne.
Un point est commun aux 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 le port 443, l’émission échoue d’une manière qui ressemble à un problème DNS.
Le même routage de deux applications dans trois configurations
L’objectif : app.example.com est envoyé vers un service sur 127.0.0.1:8080, et files.example.com vers un service sur 127.0.0.1:8081, dans les deux cas via HTTPS. Voici la configuration complète pour chaque proxy, afin de rendre visible la différence de longueur au lieu 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;
}
}Reliez ensuite le fichier, testez la configuration, 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 alors vos utilisateurs vers localhost. Le rôle de chacun de ces quatre en-têtes, ainsi que la raison pour laquelle une barre oblique finale dans proxy_pass modifie discrètement le chemin reçu par l’application, sont expliqués directive par directive dans ce guide consacré à un bloc server nginx.
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 définit 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 à votre backend sur son origine. Les certificats, la redirection du port 80 et le renouvellement découlent tous deux des adresses des sites. Rien d’autre dans le fichier ne les demande.
Traefik
Traefik a besoin d’une configuration statique avant de pouvoir router les requêtes. Sous forme de service Compose, avec le tag d’image à jour 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 porte ensuite son propre routage, défini par des 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 désigne 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:, et c’est là le véritable avantage : seul Traefik est publié. La configuration complète, avec le réseau partagé et le middleware de redirection, est présentée dans ce guide du routage de plusieurs applications avec Traefik et Docker Compose.
Quel volume 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 hostname. Le bloc site Caddy contient 3 lignes. Traefik nécessite 17 lignes de configuration statique avant de traiter une seule requête, puis 5 labels par application.
Ne vous arrêtez pas au gagnant. Traefik coûte le plus avant 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-dessus, les labels prennent l’avantage et le conservent, car le routage se trouve à côté du service concerné. Si vous supprimez le service, sa route disparaît avec lui. C’est précisément ce que gère mal un fichier de configuration centralisé : les blocs server obsolètes d’applications supprimées depuis plusieurs mois.
Le nombre de lignes avantage également Nginx. Chacun de ces blocs nécessite un lien symbolique, un nginx -t, un rechargement et une exécution de certbot, tandis qu’une modification de Caddy nécessite un seul rechargement et qu’une modification de Traefik ne nécessite aucune commande. Les trois solutions se rechargent sans interrompre les connexions actives. La différence tient au nombre d’étapes distinctes dont vous devez vous souvenir à une heure du matin.
Laquelle connaît vos conteneurs ?
Traefik surveille le socket Docker et construit les routeurs à partir des labels des conteneurs lorsque ceux-ci démarrent ou s’arrêtent. Aucun autre outil présenté ici ne fait cela. 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 énoncer clairement. Traefik lit /var/run/docker.sock. Toute personne capable de communiquer avec ce socket peut démarrer un conteneur en y montant le système de fichiers de l’hôte, ce qui lui donne les privilèges root sur l’hôte. Un montage en lecture seule réduit le risque, sans le supprimer. Si ce risque est important pour votre modèle de menace, interposez 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 communautaire. Toutefois, les plugins Caddy sont intégrés à la compilation. Vous devez donc construire un binaire personnalisé ou une image personnalisée avec xcaddy, puis assurer la maintenance de cette build et de 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 sans configuration explicite.
# /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 elles manquent, la console du navigateur affiche WebSocket connection to 'wss://app.example.com/ws' failed, tandis que le journal du backend indique un GET ordinaire. La variable map est nécessaire, car une valeur Connection: upgrade codée en dur serait envoyée avec chaque requête, y compris les requêtes ordinaires qui devraient contenir close.
Deux autres valeurs par défaut de Nginx posent problème. proxy_read_timeout est défini à 60 secondes et s’applique au tunnel après l’upgrade : un WebSocket sans trafic pendant une minute est donc fermé par le proxy. De même, les événements envoyés par le serveur arrivent en retard ou par rafales 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 votre page l’attend.
Caddy effectue l’upgrade et transforme la connexion en tunnel bidirectionnel sans aucune directive. Il envoie aussi 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 upgrades et ne bufferise pas les réponses, sauf si vous ajoutez vous-même son middleware buffering. Si vos services incluent un chat, un terminal web, un suivi de logs ou des tableaux de bord en temps réel, cela change concrètement la quantité de configuration à écrire et à déboguer.
Bloc server 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 se trouver 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 de quelque chose de moins courant ?
C’est dans ce type de situation que Nginx justifie ses lignes de configuration supplémentaires.
- Certificats client, également appelés mTLS (mutual TLS), 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 de l’exprimer : vous définissez une option TLS dans un file provider, puis vous l’associez au router avectraefik.http.routers.app.tls.options=mtls@file. Le modèle où tout est défini dans les labels connaît sa première exception dès que vous avez besoin de cette fonction. - Envois volumineux. Nginx limite par défaut le corps des requêtes à 1 MB. Un envoi plus volumineux renvoie
413 Request Entity Too Large, et l’error log indiqueclient intended to send too large body. Augmentezclient_max_body_size. Caddy et Traefik n’imposent aucune limite par défaut sur le corps des requêtes. La requête atteint donc votre application, qui applique sa propre limite. - Mise en cache des réponses. Nginx dispose de
proxy_cache, une fonction mature. Caddy nécessite un plugin compilé. La build open source de Traefik ne fournit aucun cache HTTP, ce qui surprend les personnes qui supposent que tous les proxies mettent les réponses en cache. - TCP ou UDP bruts, pour un port de 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, donc une autre build 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 ajoute alors deux endroits qui définissent des headers et deux endroits qui peuvent réécrire une URL. Décidez lequel termine TLS, puis gardez l’autre en HTTP simple, lié à loopback.
Le piège du pare-feu qui découle de ce choix
L’objectif d’un reverse proxy est de n’ouvrir que les ports 80 et 443. Docker contourne cela 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 le port publié 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.
Testez depuis une machine qui n’est pas le VPS, car un test exécuté directement sur le serveur réussit toujours :
curl --max-time 5 http://your.server.address:8080Connection refused ou un délai d’attente est le résultat attendu. Une réponse HTTP signifie que cette 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 tâche récurrente la plus contraignante. La configuration reste assez 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, vous disposez de moins de réponses prêtes à copier-coller lorsque quelque chose se comporte de manière inattendue.
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 entraîne celle de sa route. Prévoyez un après-midi pour la première configuration, car entrypoints, routers, services et middlewares constituent un vocabulaire nouveau. Une erreur de syntaxe dans un label se manifeste généralement par une erreur 404 renvoyée par Traefik plutôt que par un échec au démarrage. Consultez donc docker logs traefik pour voir l’erreur d’analyse avant de conclure que l’application est défaillante.
Une configuration Nginx existante, ou l’une des exigences listées plus haut : Nginx. Il propose déjà une solution pour la mise en cache des réponses et les certificats clients, et presque tous les guides tiers partent de ce principe. En contrepartie, vous devez configurer les certificats et la prise en charge de WebSocket 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 rentable, 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 panne. 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 ne nécessite-t-il vraiment 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 doivent toutefois être remplies. 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 les ports 80 et 443, et placez tout le reste derrière lui. Si vous effectuez une migration, déplacez les hostnames un par un : demandez au proxy frontal de transmettre les requêtes à l’ancien proxy sur un port loopback jusqu’à ce que le dernier site soit migré.
Pourquoi mes websockets sont-ils déconnectés après 60 secondes derrière Nginx ?
proxy_read_timeout est défini par défaut à 60 secondes 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 pour cet emplacement 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 effectué un upgrade après un délai fixe d’une minute. C’est pourquoi la même application peut sembler stable derrière ces proxies et instable derrière Nginx.