Traefik vs Caddy vs Nginx : reverse proxy Docker comparé

13 min de lecture·Matthieu·docker-composelets-encryptreverse-proxynginxcaddytraefikdocker|

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 :

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=true active ce conteneur (puisque exposedbydefault=false est défini)
  • traefik.http.routers.whoami.rule=Host(...) filtre les requêtes par nom d'hôte
  • traefik.http.routers.whoami.tls.certresolver=letsencrypt demande à 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 :

  1. Vous faites tourner beaucoup de conteneurs qui changent souvent (ajout/suppression de services chaque semaine)
  2. Vous voulez du routage sans intervention : déployez un conteneur avec des labels et il est en ligne
  3. Vous utilisez Docker Swarm ou avez besoin de découverte de services sur plusieurs nœuds
  4. Vous acceptez l'exposition du socket Docker (avec un proxy de socket en production)

Choisissez Caddy quand :

  1. Vous faites tourner quelques services qui changent rarement
  2. Vous voulez le chemin le plus simple vers le HTTPS automatique
  3. Vous ne voulez pas monter le socket Docker
  4. Vous privilégiez une image légère et une faible empreinte mémoire
  5. Vous voulez le support HTTP/3 sans configuration supplémentaire

Choisissez Nginx quand :

  1. Vous connaissez déjà la configuration Nginx
  2. Vous avez besoin d'un contrôle fin sur le comportement du proxy (buffers, cache, en-têtes personnalisés par location)
  3. Vous voulez la consommation mémoire la plus basse possible
  4. Votre équipe infrastructure a des outils et du monitoring Nginx existants
  5. Gérer Certbot séparément ne vous dérange pas

Arbre de décision :

  1. Vous faites tourner plus de 5 services Docker qui changent régulièrement ? Oui -> Traefik
  2. Vous avez besoin d'un réglage fin du proxy ou vous utilisez déjà Nginx ? Oui -> Nginx
  3. 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