Traefik vs Caddy vs Nginx : reverse proxy Docker comparé
Trois stacks Docker Compose fonctionnels pour Traefik, Caddy et Nginx en reverse proxy sur un VPS. Même backend, benchmarks réels et un guide de décision pour choisir le bon.
Vous avez des conteneurs Docker qui tournent sur un VPS. Vous avez besoin de HTTPS, de routage par nom de domaine et d'un point d'entrée unique. Traefik, Caddy et Nginx résolvent tous ce problème. Ils le résolvent différemment.
Cet article vous donne trois stacks Docker Compose fonctionnels qui déploient le même backend derrière chaque proxy. Copiez celui qui correspond à votre situation. Le tableau comparatif et le guide de décision en fin d'article vous aident à choisir.
Tous les exemples utilisent un réseau proxy dédié, une redirection HTTP vers HTTPS et des paramètres adaptés à la production. Les stacks ciblent Ubuntu 24.04 sur un VPS Virtua Cloud.
Que fait un reverse proxy pour des conteneurs Docker sur un VPS ?
Un reverse proxy se place entre internet et vos conteneurs Docker. Il termine le TLS (HTTPS), route les requêtes vers le bon conteneur selon le nom d'hôte et expose une seule paire de ports (80/443) au lieu d'un port par service. Vos conteneurs ne gèrent jamais les certificats et ne se lient jamais directement aux ports publics.
Sans reverse proxy, chaque conteneur aurait besoin de son propre port public. Les visiteurs accéderaient à example.com:3000 pour un service et example.com:8080 pour un autre. Un reverse proxy permet d'utiliser app.example.com et api.example.com sur les ports standards.
Les trois stacks ci-dessous supposent que :
- Docker et Docker Compose sont installés Docker en production sur un VPS : ce qui casse et comment corriger
- Un enregistrement DNS A pointe votre domaine vers l'IP du VPS
- Les ports 80 et 443 sont ouverts dans votre pare-feu Docker contourne UFW : 4 solutions testées pour votre VPS
- Vous avez un accès SSH en tant qu'utilisateur non-root avec sudo
Chaque exemple déploie le même backend : l'image traefik/whoami, qui renvoie les en-têtes HTTP et les informations du conteneur. Remplacez-la par votre vraie application ensuite.
Comment configurer Traefik comme reverse proxy Docker avec HTTPS automatique ?
Traefik découvre les conteneurs automatiquement en lisant les labels Docker. Vous ajoutez les règles de routage comme labels sur chaque service. Quand un conteneur démarre, Traefik le détecte, demande un certificat Let's Encrypt et commence à router le trafic. Pas besoin de recharger la configuration.
Créez le répertoire du projet :
mkdir -p ~/traefik-proxy && cd ~/traefik-proxy
Créez le réseau Docker que tous les services proxifiés partageront :
docker network create proxy
Créez docker-compose.yml :
services:
traefik:
image: traefik:v3.6
container_name: traefik
restart: unless-stopped
command:
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--providers.docker.network=proxy"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--entrypoints.web.http.redirections.entrypoint.to=websecure"
- "--entrypoints.web.http.redirections.entrypoint.scheme=https"
- "--certificatesresolvers.letsencrypt.acme.email=you@example.com"
- "--certificatesresolvers.letsencrypt.acme.storage=/acme.json"
- "--certificatesresolvers.letsencrypt.acme.tlschallenge=true"
- "--log.level=WARN"
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./acme.json:/acme.json
networks:
- proxy
security_opt:
- no-new-privileges:true
whoami:
image: traefik/whoami
container_name: whoami
restart: unless-stopped
labels:
- "traefik.enable=true"
- "traefik.http.routers.whoami.rule=Host(`app.example.com`)"
- "traefik.http.routers.whoami.entrypoints=websecure"
- "traefik.http.routers.whoami.tls.certresolver=letsencrypt"
networks:
- proxy
networks:
proxy:
external: true
Avant de démarrer, créez le fichier de stockage des certificats avec des permissions restreintes :
touch acme.json && chmod 600 acme.json
Traefik refuse de démarrer si acme.json a des permissions trop ouvertes. Le 600 garantit que seul le propriétaire peut lire les clés privées stockées à l'intérieur.
Lancez le stack :
docker compose up -d
Vérifiez que les deux conteneurs tournent :
docker compose ps
traefik et whoami doivent afficher le statut Up. Testez maintenant depuis votre machine locale (pas le serveur) :
curl https://app.example.com
La réponse contient la sortie de whoami avec les en-têtes de la requête. L'en-tête X-Forwarded-For dans la réponse vous indique que Traefik proxifie le trafic et termine le TLS.
Ce que font les labels :
traefik.enable=trueactive ce conteneur (puisqueexposedbydefault=falseest défini)traefik.http.routers.whoami.rule=Host(...)filtre les requêtes par nom d'hôtetraefik.http.routers.whoami.tls.certresolver=letsencryptdemande à Traefik d'obtenir un certificat pour ce domaine
Pour ajouter un autre service, ajoutez-le dans n'importe quel fichier Compose sur le même réseau proxy avec les bons labels. Traefik le détecte automatiquement.
Est-il sûr de monter le socket Docker dans Traefik ?
Monter /var/run/docker.sock donne à Traefik un accès complet à l'API Docker. Si un attaquant compromet Traefik, il peut créer des conteneurs, lire les variables d'environnement (y compris les secrets) et escalader vers root sur l'hôte. Le flag :ro empêche seulement les écritures au niveau du système de fichiers. Il ne restreint pas les appels à l'API Docker.
En production, utilisez un proxy de socket Docker. Il se place entre Traefik et le démon Docker, filtrant les appels API pour n'autoriser que les opérations de lecture sur les métadonnées des conteneurs.
Ajoutez ceci à votre docker-compose.yml :
services:
socket-proxy:
image: tecnativa/docker-socket-proxy:0.4
container_name: socket-proxy
restart: unless-stopped
environment:
CONTAINERS: 1
NETWORKS: 1
SERVICES: 0
TASKS: 0
POST: 0
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
networks:
- socket-proxy
security_opt:
- no-new-privileges:true
traefik:
image: traefik:v3.6
container_name: traefik
restart: unless-stopped
depends_on:
- socket-proxy
command:
- "--providers.docker.endpoint=tcp://socket-proxy:2375"
- "--providers.docker.exposedbydefault=false"
- "--providers.docker.network=proxy"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--entrypoints.web.http.redirections.entrypoint.to=websecure"
- "--entrypoints.web.http.redirections.entrypoint.scheme=https"
- "--certificatesresolvers.letsencrypt.acme.email=you@example.com"
- "--certificatesresolvers.letsencrypt.acme.storage=/acme.json"
- "--certificatesresolvers.letsencrypt.acme.tlschallenge=true"
- "--log.level=WARN"
ports:
- "80:80"
- "443:443"
volumes:
- ./acme.json:/acme.json
networks:
- proxy
- socket-proxy
security_opt:
- no-new-privileges:true
networks:
proxy:
external: true
socket-proxy:
driver: bridge
internal: true
Traefik ne monte plus le socket Docker directement. Le réseau socket-proxy est internal: true, ce qui signifie qu'il n'a pas d'accès internet sortant. Le proxy de socket n'autorise que les requêtes GET sur les endpoints containers et networks.
Comment configurer Caddy comme reverse proxy Docker avec HTTPS automatique ?
Caddy gère le HTTPS automatiquement sans aucune configuration au-delà du nom de domaine. Pointez un domaine vers votre serveur, mettez-le dans le Caddyfile, et Caddy obtient et renouvelle les certificats depuis Let's Encrypt. Pas de configuration de résolveur, pas de paramètres ACME. C'est le chemin le plus court vers le HTTPS pour un reverse proxy Docker.
Créez le répertoire du projet :
mkdir -p ~/caddy-proxy && cd ~/caddy-proxy
Créez le réseau proxy partagé (passez cette étape si vous l'avez déjà créé pour Traefik) :
docker network create proxy
Créez le Caddyfile :
app.example.com {
reverse_proxy whoami:80
encode gzip
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"
}
}
Voilà toute la configuration du proxy. Caddy lit le nom de domaine, demande un certificat et proxifie vers le conteneur whoami sur le port 80. Pas de résolveur de certificat, pas d'email ACME (Caddy utilise celui par défaut de votre machine, ou vous pouvez le définir globalement), pas de chemins de stockage à gérer.
Créez docker-compose.yml :
services:
caddy:
image: caddy:2.11
container_name: caddy
restart: unless-stopped
ports:
- "80:80"
- "443:443"
- "443:443/udp"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data
- caddy_config:/config
networks:
- proxy
cap_drop:
- ALL
cap_add:
- NET_BIND_SERVICE
whoami:
image: traefik/whoami
container_name: whoami
restart: unless-stopped
networks:
- proxy
networks:
proxy:
external: true
volumes:
caddy_data:
caddy_config:
Le port 443:443/udp active HTTP/3 (QUIC), que Caddy supporte nativement. Le cap_drop: ALL avec cap_add: NET_BIND_SERVICE supprime toutes les capabilities Linux sauf celle nécessaire pour se lier aux ports inférieurs à 1024.
Lancez le stack :
docker compose up -d
Vérifiez le statut des conteneurs :
docker compose ps
Les deux conteneurs doivent afficher Up. Testez depuis votre machine locale avec une sortie verbeuse :
curl -v https://app.example.com
Cherchez HTTP/2 200 dans la sortie. Vous devriez aussi voir les en-têtes de sécurité du Caddyfile (Strict-Transport-Security, X-Content-Type-Options, etc.).
Pour ajouter un autre service, ajoutez un nouveau bloc dans le Caddyfile avec le domaine et la directive reverse_proxy, puis rechargez :
docker compose exec caddy caddy reload --config /etc/caddy/Caddyfile
Pas besoin de redémarrer le conteneur. Caddy n'a pas besoin du socket Docker. Il ne découvre pas automatiquement les conteneurs. Vous gérez le routage dans le Caddyfile.
Comment configurer Nginx comme reverse proxy Docker avec Let's Encrypt ?
Nginx vous donne un contrôle total sur chaque directive de proxy, en-tête, taille de buffer et règle de cache. La contrepartie : une configuration manuelle. Nginx n'obtient pas les certificats TLS tout seul. Vous l'associez avec Certbot, qui gère les challenges ACME et le renouvellement des certificats.
Créez le répertoire du projet :
mkdir -p ~/nginx-proxy && cd ~/nginx-proxy
Créez le réseau proxy partagé :
docker network create proxy
Créez la configuration Nginx dans nginx/conf.d/app.conf :
mkdir -p nginx/conf.d
server {
listen 80;
server_name app.example.com;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl;
http2 on;
server_name app.example.com;
ssl_certificate /etc/letsencrypt/live/app.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/app.example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers off;
server_tokens off;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
location / {
proxy_pass http://whoami:80;
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;
}
}
server_tokens off; masque la version de Nginx dans les en-têtes de réponse. La divulgation de version aide les attaquants à cibler des vulnérabilités connues.
Créez docker-compose.yml :
services:
nginx:
image: nginx:1.28
container_name: nginx
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/conf.d:/etc/nginx/conf.d:ro
- certbot_webroot:/var/www/certbot:ro
- certbot_certs:/etc/letsencrypt:ro
networks:
- proxy
depends_on:
- whoami
certbot:
image: certbot/certbot
container_name: certbot
restart: unless-stopped
volumes:
- certbot_webroot:/var/www/certbot
- certbot_certs:/etc/letsencrypt
entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;'"
whoami:
image: traefik/whoami
container_name: whoami
restart: unless-stopped
networks:
- proxy
networks:
proxy:
external: true
volumes:
certbot_webroot:
certbot_certs:
Nginx exige que les fichiers de certificats existent avant de démarrer. La configuration ci-dessus référence /etc/letsencrypt/live/app.example.com/fullchain.pem, qui n'existe pas encore. Pour le certificat initial, remplacez temporairement app.conf par une version HTTP uniquement :
server {
listen 80;
server_name app.example.com;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://$host$request_uri;
}
}
Démarrez Nginx et le backend :
docker compose up -d nginx whoami
Demandez le certificat initial :
docker compose run --rm certbot certonly \
--webroot \
--webroot-path=/var/www/certbot \
-d app.example.com \
--email you@example.com \
--agree-tos \
--no-eff-email
Une fois le certificat obtenu, restaurez le app.conf complet (la version avec le bloc serveur SSL montrée ci-dessus), puis lancez le stack complet :
docker compose up -d
Vérifiez que tous les conteneurs tournent :
docker compose ps
Testez depuis votre machine locale :
curl -v https://app.example.com
L'en-tête de réponse server: devrait afficher nginx sans numéro de version, ce qui confirme que server_tokens off est actif.
Pour ajouter un autre service, créez un nouveau fichier .conf dans nginx/conf.d/, puis rechargez :
docker compose exec nginx nginx -s reload
Pour le renouvellement des certificats, le conteneur Certbot exécute certbot renew toutes les 12 heures. Après le renouvellement, rechargez Nginx pour prendre en compte les nouveaux certificats. Automatisez cela avec un cron job ou un script qui vérifie les dates de modification des certificats. Pour aller plus loin dans la configuration Nginx en reverse proxy, consultez Configurer Nginx comme reverse proxy.
Comment Traefik, Caddy et Nginx se comparent-ils pour le reverse proxying Docker ?
Traefik l'emporte sur la découverte automatique. Caddy gagne en simplicité. Nginx gagne en contrôle. Le tableau ci-dessous détaille les compromis qui comptent quand vous faites tourner des conteneurs Docker sur un VPS.
| Fonctionnalité | Traefik v3 | Caddy 2.11 | Nginx 1.28 |
|---|---|---|---|
| Découverte auto | Oui (labels Docker) | Non (Caddyfile manuel) | Non (fichiers conf manuels) |
| Automatisation TLS | ACME intégré | ACME intégré | Nécessite un sidecar Certbot |
| Méthode de config | Labels Docker + YAML/CLI statique | Caddyfile ou API JSON | Fichiers nginx.conf |
| Rechargement config | Automatique sur événements conteneur | caddy reload (sans interruption) |
nginx -s reload (sans interruption) |
| Socket Docker requis | Oui (ou proxy de socket) | Non | Non |
| HTTP/3 (QUIC) | Expérimental | Oui (par défaut) | Via module tiers |
| Middleware/plugins | Intégré (rate limit, auth, headers) | Intégré + plugins Go | Via directives de config |
| Communauté/docs | Large, active, bonne doc | Plus petite, excellente doc | La plus large, doc étendue |
| Courbe d'apprentissage | Moyenne (labels + config statique) | Faible (Caddyfile intuitif) | Élevée (nombreuses directives) |
Quel reverse proxy consomme le moins de mémoire ?
La consommation mémoire au repos compte sur un VPS où chaque mégaoctet est précieux. Ces chiffres proviennent de docker stats --no-stream sur un VPS Virtua Cloud 4 vCPU / 8 Go de RAM sous Ubuntu 24.04. Chaque proxy tournait au repos sans trafic avant la mesure.
| Proxy | RAM au repos | Taille de l'image |
|---|---|---|
| Traefik v3.6 | ~17 Mo | ~242 Mo |
| Caddy 2.11 | ~14 Mo | ~88 Mo |
| Nginx 1.28 | ~5 Mo | ~240 Mo |
| Nginx + Certbot | ~5 Mo + ~25 Mo | ~240 Mo + ~298 Mo |
Nginx consomme de loin le moins de mémoire. Caddy se situe au milieu. La consommation plus élevée de Traefik vient du maintien en mémoire de l'état du provider Docker et de la table de routage. Les trois utilisent les images par défaut (basées sur Debian/Alpine). Les variantes Alpine réduiraient la taille des images au prix de problèmes de compatibilité potentiels avec certaines extensions.
Sous charge légère (100 requêtes concurrentes via wrk), les trois gèrent le trafic sans augmentation significative du CPU ou de la mémoire sur cette taille de VPS. Les différences ne comptent qu'à grande échelle ou sur les plus petits plans VPS.
Comment choisir le bon reverse proxy pour votre configuration Docker ?
Le bon choix dépend du nombre de services que vous faites tourner, de la fréquence à laquelle ils changent et de ce que vous connaissez déjà.
Choisissez Traefik quand :
- Vous faites tourner beaucoup de conteneurs qui changent souvent (ajout/suppression de services chaque semaine)
- Vous voulez du routage sans intervention : déployez un conteneur avec des labels et il est en ligne
- Vous utilisez Docker Swarm ou avez besoin de découverte de services sur plusieurs nœuds
- Vous acceptez l'exposition du socket Docker (avec un proxy de socket en production)
Choisissez Caddy quand :
- Vous faites tourner quelques services qui changent rarement
- Vous voulez le chemin le plus simple vers le HTTPS automatique
- Vous ne voulez pas monter le socket Docker
- Vous privilégiez une image légère et une faible empreinte mémoire
- Vous voulez le support HTTP/3 sans configuration supplémentaire
Choisissez Nginx quand :
- Vous connaissez déjà la configuration Nginx
- Vous avez besoin d'un contrôle fin sur le comportement du proxy (buffers, cache, en-têtes personnalisés par location)
- Vous voulez la consommation mémoire la plus basse possible
- Votre équipe infrastructure a des outils et du monitoring Nginx existants
- Gérer Certbot séparément ne vous dérange pas
Arbre de décision :
- Vous faites tourner plus de 5 services Docker qui changent régulièrement ? Oui -> Traefik
- Vous avez besoin d'un réglage fin du proxy ou vous utilisez déjà Nginx ? Oui -> Nginx
- Vous voulez le moins de pièces mobiles et la mise en place la plus rapide ? Oui -> Caddy
Pour la plupart des indie hackers qui déploient un ou deux projets perso, Caddy est le meilleur point de départ. Pour les équipes DevOps qui gèrent une flotte de conteneurs, la découverte automatique de Traefik se rentabilise. Pour les équipes qui utilisent déjà Nginx ailleurs, rester sur Nginx garde votre stack cohérent Réseau Docker sur un VPS : bridge, host et macvlan expliqués.
Durcissement de la sécurité pour les trois proxys
Quel que soit le proxy que vous choisissez, appliquez ces pratiques de sécurité de base.
En-têtes de sécurité. Les trois exemples ci-dessus incluent HSTS, X-Content-Type-Options, X-Frame-Options et Referrer-Policy. Pour Traefik, ajoutez-les comme labels middleware :
labels:
- "traefik.http.middlewares.security-headers.headers.stsSeconds=31536000"
- "traefik.http.middlewares.security-headers.headers.stsIncludeSubdomains=true"
- "traefik.http.middlewares.security-headers.headers.contentTypeNosniff=true"
- "traefik.http.middlewares.security-headers.headers.frameDeny=true"
- "traefik.http.routers.whoami.middlewares=security-headers"
Limitation de débit. Traefik dispose d'un middleware de rate limiting intégré. Caddy a une directive rate_limit disponible comme plugin. Nginx utilise limit_req_zone dans sa configuration. La limitation de débit protège votre backend contre les attaques par force brute et les abus.
Isolation réseau Docker. Chaque exemple utilise un réseau proxy externe. Les services backend ne doivent pas être sur le réseau bridge par défaut. Seuls les conteneurs qui doivent être proxifiés rejoignent le réseau proxy. Les conteneurs de base de données et les services internes restent sur des réseaux séparés et internes Durcissement de la sécurité Docker : mode rootless, Seccomp et AppArmor sur un VPS.
Pare-feu. Seuls les ports 80 et 443 doivent être accessibles publiquement. Docker manipule iptables directement, ce qui peut contourner les règles UFW. Consultez Docker contourne UFW : 4 solutions testées pour votre VPS pour la solution.
Journaux. Consultez les journaux du proxy quand quelque chose ne fonctionne pas :
# Traefik
docker logs traefik -f
# Caddy
docker logs caddy -f
# Nginx
docker logs nginx -f
Pour Traefik, définissez --log.level=DEBUG temporairement pour diagnostiquer les problèmes de routage ou de certificat. Pour Caddy, activez l'option globale debug dans le Caddyfile. Pour Nginx, consultez error.log à l'intérieur du conteneur dans /var/log/nginx/error.log.
Un problème ?
| Symptôme | Cause probable | Solution |
|---|---|---|
| Certificat non émis | Enregistrement DNS A ne pointe pas vers l'IP du VPS | Vérifiez avec dig app.example.com |
| Traefik 404 sur toutes les routes | Conteneur pas sur le réseau proxy |
Vérifiez docker network inspect proxy |
| Caddy « permission denied » sur le port 80 | Capability NET_BIND_SERVICE manquante |
Ajoutez cap_add: NET_BIND_SERVICE |
| Nginx « no such file » pour le certificat | Certbot n'a pas encore tourné | Exécutez certbot certonly d'abord |
ERR_CONNECTION_REFUSED |
Pare-feu bloquant 80/443 | Vérifiez ufw status ou iptables -L |
Erreur de permissions acme.json Traefik |
Permissions du fichier trop ouvertes | Exécutez chmod 600 acme.json |
| Le proxy fonctionne sur le serveur, échoue depuis l'extérieur | Test sur localhost uniquement | Testez avec curl depuis votre machine locale |
Pour le durcissement en production au-delà du reverse proxying, consultez Limites de ressources, healthchecks et politiques de redémarrage Docker Compose pour les limites de ressources et les health checks sur vos stacks Compose.
Copyright 2026 Virtua.Cloud. Tous droits réservés. Ce contenu est une création originale de l'équipe Virtua.Cloud. Toute reproduction, republication ou redistribution sans autorisation écrite est interdite.
Prêt à essayer ?
Déployez votre serveur en quelques secondes. Linux, Windows ou FreeBSD.
Voir les offres VPS