Traefik
La configuration d'un reverse proxy traditionnel comme Nginx exige une intervention manuelle à chaque ajout ou modification d'un service : éditer un fichier de configuration, tester la syntaxe, recharger le process. Dans un environnement conteneurisé où les services démarrent et s'arrêtent fréquemment, cette approche ne tient pas. Traefik lit la liste des services directement dans Docker ou Kubernetes et régénère sa configuration à la volée, sans interruption de service.
Configuration statique et dynamique
Traefik distingue deux niveaux de configuration. La configuration statique est lue une seule fois, au lancement : elle déclare les entrypoints (ports d'écoute), les providers (sources de configuration) et les certificate resolvers (ACME). La modifier impose un redémarrage. La configuration dynamique regroupe les routers, services et middlewares ; Traefik la recharge à chaud depuis les labels Docker, les ressources Kubernetes (Ingress, CRD IngressRoute) ou des fichiers surveillés par le provider File.
Configuration statique (flags CLI / traefik.yml)
└── Entrypoints (ports 80, 443)
└── Providers (Docker, Kubernetes, File)
└── Certificate resolvers (Let's Encrypt)
Configuration dynamique (labels, annotations, fichiers)
└── Routers (règles de matching)
└── Middlewares (transformations)
└── Services (backends)
Un router fait le matching entre une requête entrante et un service. Une règle de router combine des prédicats avec &&, || et ! : Host(`api.example.com`), PathPrefix(`/api`), Method(`GET`), Header(`X-Api-Key`, `secret`) ou HeaderRegexp(`X-Api-Key`, `.+`). Traefik v3 a renommé les matchers Headers et HeadersRegexp de la v2 en Header et HeaderRegexp.
Quand plusieurs routers correspondent à une même requête, Traefik retient celui de plus haute priorité. Par défaut, la priorité est égale à la longueur de la règle : Host(`example.com`) && PathPrefix(`/api`) l'emporte sur Host(`example.com`). Le label traefik.http.routers.<nom>.priority force une valeur explicite.
Déploiement avec Docker
Traefik lit les labels des conteneurs par l'API Docker, via le socket /var/run/docker.sock.
services:
traefik:
image: traefik:v3 # en production, épingler une version mineure précise
command:
- --api.dashboard=true
- --api.insecure=false
- --providers.docker=true
- --providers.docker.exposedByDefault=false # sécurité : opt-in par service
- --providers.docker.network=proxy # réseau Docker par défaut pour les backends
- --entrypoints.web.address=:80
- --entrypoints.web.http.redirections.entrypoint.to=websecure
- --entrypoints.web.http.redirections.entrypoint.scheme=https
- --entrypoints.websecure.address=:443
- --certificatesresolvers.letsencrypt.acme.email=admin@example.com
- --certificatesresolvers.letsencrypt.acme.storage=/acme.json
- --certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./acme.json:/acme.json
networks:
- proxy
networks:
proxy:
external: true
touch acme.json && chmod 600 acme.json
docker network create proxy
docker compose up -d
Un service s'expose avec quelques labels :
services:
api:
image: myapp:latest
networks:
- proxy
labels:
- "traefik.enable=true"
- "traefik.http.routers.api.rule=Host(`api.example.com`)"
- "traefik.http.routers.api.entrypoints=websecure"
- "traefik.http.routers.api.tls.certresolver=letsencrypt"
- "traefik.http.services.api.loadbalancer.server.port=8000"
Traefik détecte le conteneur au démarrage et configure immédiatement le router, sans redémarrer Traefik.
Accès au socket Docker
Le suffixe :ro du montage ne protège que le fichier socket lui-même : il n'empêche aucun appel à l'API Docker. Un processus qui accède au socket peut créer un conteneur privilégié montant la racine de l'hôte, ce qui équivaut à un accès root. Traefik étant exposé à Internet, une faille dans Traefik donnerait ce niveau d'accès.
Un proxy de socket réduit cette surface : il se place entre Traefik et le socket et ne laisse passer que les appels autorisés. Traefik n'a besoin que de lire la liste des conteneurs et leurs événements.
services:
socket-proxy:
image: lscr.io/linuxserver/socket-proxy:latest # en production, épingler une version précise
environment:
CONTAINERS: 1 # lecture de la liste des conteneurs
POST: 0 # aucune requête d'écriture (valeur par défaut)
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
read_only: true
tmpfs:
- /run
networks:
- socket
traefik:
image: traefik:v3
command:
- --providers.docker.endpoint=tcp://socket-proxy:2375
# autres options inchangées
networks:
- proxy
- socket
networks:
proxy:
external: true
socket:
internal: true # réseau sans accès extérieur, réservé à l'API Docker
Le montage direct du socket dans Traefik disparaît : seul le proxy y accède, et seuls Traefik et lui partagent le réseau socket.
Middlewares
Les middlewares transforment les requêtes entre le router et le backend. Ils se définissent séparément et s'attachent à un ou plusieurs routers.
Redirection HTTPS
La redirection vers HTTPS, déjà assurée par l'entrypoint dans l'exemple précédent, peut aussi passer par un middleware, par exemple pour ne l'appliquer qu'à certains routers :
labels:
- "traefik.http.middlewares.redirect-https.redirectscheme.scheme=https"
- "traefik.http.middlewares.redirect-https.redirectscheme.permanent=true"
- "traefik.http.routers.api-http.middlewares=redirect-https"
BasicAuth
# Générer le hash du mot de passe
htpasswd -nb admin secretpassword
# Résultat : admin:$apr1$...
labels:
- "traefik.http.middlewares.auth.basicauth.users=admin:$$apr1$$..." # doubler les $
- "traefik.http.routers.dashboard.middlewares=auth"
Le doublement des $ est propre aux fichiers Compose, qui interprètent $ comme le début d'une interpolation de variable ; il n'est pas nécessaire dans un fichier de configuration dynamique Traefik. htpasswd -nbB produit un hash bcrypt, également accepté par Traefik et plus résistant que l'algorithme MD5 apr1. Pour une authentification centralisée avec session et second facteur, le middleware forwardAuth délègue la décision à un service externe, comme décrit dans l'article Authelia : forward auth.
Rate limiting
labels:
- "traefik.http.middlewares.ratelimit.ratelimit.average=100" # requêtes moyennes par période (1 s par défaut)
- "traefik.http.middlewares.ratelimit.ratelimit.burst=50" # pic autorisé
- "traefik.http.routers.api.middlewares=ratelimit"
Le middleware implémente un token bucket : le seau contient au maximum burst jetons et se remplit au rythme de average jetons par period. Chaque requête consomme un jeton ; sans jeton disponible, Traefik répond 429 Too Many Requests. Par défaut, la limite s'applique par adresse IP source (sourceCriterion). Derrière un load balancer, cette adresse est celle du load balancer : Traefik ne tient compte de l'en-tête X-Forwarded-For que si l'amont figure dans --entrypoints.<nom>.forwardedHeaders.trustedIPs, et sourceCriterion.ipStrategy.depth indique alors quelle adresse de la chaîne retenir.
En-têtes de sécurité
labels:
- "traefik.http.middlewares.secheaders.headers.forceSTSHeader=true"
- "traefik.http.middlewares.secheaders.headers.stsSeconds=63072000"
- "traefik.http.middlewares.secheaders.headers.contentTypeNosniff=true"
- "traefik.http.middlewares.secheaders.headers.frameDeny=true"
- "traefik.http.routers.api.middlewares=secheaders"
Chaîner plusieurs middlewares
labels:
- "traefik.http.routers.api.middlewares=ratelimit,auth,secheaders"
Les middlewares s'exécutent dans l'ordre de la liste, et cet ordre change le comportement. Avec auth en tête, une requête sans identifiants valides est rejetée avant d'atteindre ratelimit : les tentatives de mot de passe échappent à la limitation, ce qui laisse le champ libre au brute-force. Placée avant auth, la limitation décompte toutes les requêtes, authentifiées ou non, et plafonne ces tentatives. L'inverse ne se justifie que si le service d'authentification bloque lui-même les échecs répétés, comme le fait Authelia. Pour réutiliser une séquence sur plusieurs routers, le middleware chain regroupe une liste ordonnée sous un seul nom :
labels:
- "traefik.http.middlewares.secured.chain.middlewares=ratelimit,auth,secheaders"
- "traefik.http.routers.api.middlewares=secured"
Provider File
Les labels ne conviennent pas à tout. Un middleware partagé par de nombreux services, ou un backend qui ne tourne pas dans Docker (un NAS, une machine virtuelle), se déclare dans des fichiers lus par le provider File :
command:
- --providers.file.directory=/etc/traefik/dynamic
- --providers.file.watch=true # rechargement à chaque modification
volumes:
- ./dynamic:/etc/traefik/dynamic:ro
# dynamic/common.yml
http:
middlewares:
auth:
basicAuth:
users:
- "admin:$apr1$..." # pas de doublement des $ hors Compose
routers:
nas:
rule: Host(`nas.example.com`)
entryPoints: [websecure]
service: nas
middlewares: [auth]
tls:
certResolver: letsencrypt
services:
nas:
loadBalancer:
servers:
- url: "http://10.0.0.20:5000"
Chaque provider a son espace de noms. Depuis un label Docker, un middleware déclaré dans ces fichiers se référence avec le suffixe du provider : traefik.http.routers.api.middlewares=auth@file. Sans suffixe, Traefik le cherche dans le provider du router et ne le trouve pas.
Routing TCP
Traefik peut aussi proxifier du trafic TCP (base de données, MQTT, etc.) via le provider Docker.
services:
postgres:
image: postgres:16
networks:
- proxy
labels:
- "traefik.enable=true"
- "traefik.tcp.routers.postgres.rule=HostSNI(`*`)" # sans TLS : accepter tout
- "traefik.tcp.routers.postgres.entrypoints=postgres"
- "traefik.tcp.services.postgres.loadbalancer.server.port=5432"
# Dans la configuration statique Traefik
command:
- --entrypoints.postgres.address=:5432
HostSNI(`*`) est la seule valeur acceptée sans TLS : sans handshake TLS, aucun nom de domaine n'est disponible, et l'entrypoint postgres ne peut alors servir qu'un seul backend. Avec TLS, HostSNI filtre sur le nom de serveur annoncé dans l'extension SNI, ce qui permet de router plusieurs services TLS sur le même port. L'option traefik.tcp.routers.<nom>.tls.passthrough=true transmet le flux chiffré au backend sans le déchiffrer.
Déploiement Kubernetes (IngressRoute)
En Kubernetes, Traefik expose une CRD IngressRoute plus expressive que l'API Ingress standard. Les middlewares y sont des ressources à part entière, de type Middleware, que l'IngressRoute référence par leur nom :
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: ratelimit
namespace: default
spec:
rateLimit:
average: 100
burst: 50
---
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: api-route
namespace: default
spec:
entryPoints:
- websecure
routes:
- match: Host(`api.example.com`) && PathPrefix(`/v2`)
kind: Rule
middlewares:
- name: ratelimit
services:
- name: api-service
port: 8000
tls:
certResolver: letsencrypt
# Installation via Helm
helm repo add traefik https://traefik.github.io/charts
helm install traefik traefik/traefik \
--set ingressRoute.dashboard.enabled=false \
--set ports.web.http.redirections.entryPoint.to=websecure \
--set ports.web.http.redirections.entryPoint.scheme=https
Les versions récentes du chart ont remplacé l'ancienne clé ports.web.redirectTo par la structure ports.<entrypoint>.http.redirections, calquée sur la configuration statique de Traefik.
Certificate Resolvers
Traefik prend en charge les trois challenges ACME de Let's Encrypt, dont le fonctionnement est détaillé dans l'article SSL/TLS :
| Challenge | Option Traefik | Port utilisé | Wildcard | Contrainte |
|---|---|---|---|---|
| HTTP-01 | httpchallenge | 80 | non | le domaine doit résoudre vers l'IP publique de la machine |
| TLS-ALPN-01 | tlschallenge | 443 | non | validation dans un handshake TLS (protocole ALPN acme-tls/1), utile quand le port 80 est fermé |
| DNS-01 | dnschallenge | aucun | oui | nécessite un accès à l'API du fournisseur DNS |
Avec DNS-01, Traefik prouve la possession du domaine en créant un enregistrement TXT dans sa zone. La machine n'a donc pas besoin d'être joignable depuis Internet : le challenge fonctionne derrière un NAT et permet d'obtenir des certificats wildcard (*.example.com). Il suppose une intégration avec le fournisseur DNS (Route53, Cloudflare, OVH, etc.).
command:
- --certificatesresolvers.cloudflare.acme.dnschallenge=true
- --certificatesresolvers.cloudflare.acme.dnschallenge.provider=cloudflare
- --certificatesresolvers.cloudflare.acme.email=admin@example.com
- --certificatesresolvers.cloudflare.acme.storage=/acme.json
environment:
CF_DNS_API_TOKEN: your_cloudflare_api_token # permissions Zone / Zone / Read et Zone / DNS / Edit
Traefik délègue les challenges DNS à la bibliothèque lego : chaque provider lit ses identifiants dans des variables d'environnement spécifiques. Pour Cloudflare, un jeton d'API limité à la lecture de la zone et à l'édition de ses enregistrements DNS (CF_DNS_API_TOKEN) réduit l'impact d'une fuite par rapport à la clé d'API globale du compte (CF_API_EMAIL + CF_API_KEY).
Par défaut, Traefik demande un certificat par nom d'hôte, déduit de la règle Host du router. Pour obtenir un seul certificat wildcard partagé par tous les sous-domaines, le domaine se déclare explicitement sur un router :
labels:
- "traefik.http.routers.api.tls.certresolver=cloudflare"
- "traefik.http.routers.api.tls.domains[0].main=example.com"
- "traefik.http.routers.api.tls.domains[0].sans=*.example.com"