Aller au contenu
medium

Traefik SSL/TLS : Let's Encrypt, ACME et certificats automatiques

23 min de lecture

Logo traefik

Ce guide vous accompagne dans la configuration de certificats TLS automatiques avec Traefik v3.7.6. Vous apprendrez à utiliser Let's Encrypt via le protocole ACME pour obtenir des certificats gratuits, que ce soit par HTTP Challenge (le plus simple) ou DNS Challenge (pour les wildcards et réseaux privés). À la fin, vos services seront accessibles en HTTPS avec des certificats renouvelés automatiquement.

Temps estimé : 20-25 minutes selon votre configuration.

Vérifiez ces points avant de commencer : un manque à ce stade se manifeste bien plus tard, sous la forme d'une erreur qui ne le désigne pas.

Vous devez avoir un nom de domaine dont les enregistrements DNS pointent vers l'IP de votre serveur Traefik :

Fenêtre de terminal
# Vérifier que votre domaine pointe vers votre serveur
dig +short mon-domaine.com A
# Doit retourner l'IP de votre serveur

Le port à ouvrir dépend du challenge retenu, et cette contrainte est souvent ce qui décide du choix. Notez la particularité du DNS-01 : il n'exige aucun port entrant, seulement un accès sortant vers l'API de votre hébergeur DNS. Un serveur derrière un pare-feu d'entreprise ne peut donc obtenir de certificat que par cette voie.

ChallengePort requisDirection
HTTP-0180Entrant depuis Internet
TLS-ALPN-01443Entrant depuis Internet
DNS-01AucunSortant vers API DNS

Vérifiez l'accessibilité depuis l'extérieur :

Fenêtre de terminal
# Depuis un autre serveur ou https://www.whatsmyip.org/port-scanner/
nc -zv votre-ip-publique 80
nc -zv votre-ip-publique 443

Ce guide suppose que Traefik est déjà installé. Si ce n'est pas le cas, consultez d'abord Installer Traefik v3.

Si vous utilisez le DNS Challenge, vous aurez besoin d'un accès API à votre provider DNS (Cloudflare, OVH, Route53, etc.).

Comprendre le protocole évite des heures de dépannage à l'aveugle : la plupart des erreurs de certificat sont en réalité des échecs de validation de domaine, pas des problèmes de configuration TLS. Cette section décrit l'échange entre Traefik et l'autorité de certification, puis les trois manières de prouver que le domaine vous appartient.

ACME (Automatic Certificate Management Environment) est le protocole utilisé par Let's Encrypt pour automatiser la délivrance de certificats. Voici comment ça fonctionne :

  1. Votre serveur demande un certificat pour un domaine (ex: api.exemple.com)
  2. Let's Encrypt vous lance un défi : "Prouvez que vous contrôlez ce domaine"
  3. Vous relevez le défi (via HTTP, DNS ou TLS selon le type choisi)
  4. Let's Encrypt vérifie et délivre le certificat

Les certificats Let's Encrypt sont gratuits et valides 90 jours. Traefik gère automatiquement le renouvellement (généralement 30 jours avant expiration).

Les trois challenges prouvent la même chose par des canaux différents. Une seule différence est structurelle plutôt que pratique : seul DNS-01 permet d'obtenir un certificat wildcard, car Let's Encrypt exige alors une preuve au niveau du domaine entier, ce qu'une requête HTTP sur un hôte donné ne peut pas fournir.

ChallengeComment ça marcheQuand l'utiliser
HTTP-01Let's Encrypt fait une requête HTTP sur /.well-known/acme-challenge/Port 80 accessible, cas le plus courant
DNS-01Vous créez un enregistrement TXT _acme-challenge.domaineWildcard, port 80 fermé, réseau privé
TLS-ALPN-01Vérification via TLS sur le port 443Port 80 fermé mais 443 ouvert

Trois éléments sont à mettre en place, et ils vivent dans deux endroits différents de Traefik. Le certificate resolver et son fichier de stockage appartiennent à la configuration statique, lue une seule fois au démarrage : toute modification impose un redémarrage. L'activation sur un router, elle, relève de la configuration dynamique, rechargée à chaud. Confondre les deux est la cause d'une bonne partie des configurations qui « ne prennent pas ».

Un Certificate Resolver est la configuration qui indique à Traefik comment obtenir des certificats. Vous le définissez dans la configuration statique :

# traefik.yaml - Configuration statique
certificatesResolvers:
letsencrypt:
acme:
email: "admin@votre-domaine.com" # Email pour les notifications
storage: /etc/traefik/acme.json # Où stocker les certificats
caServer: "https://acme-v02.api.letsencrypt.org/directory"
httpChallenge:
entryPoint: web # Entrypoint pour le challenge HTTP

Les certificats obtenus sont stockés dans un fichier JSON. Ce fichier doit avoir des permissions strictes car il contient vos clés privées :

Fenêtre de terminal
# Créer le fichier avec les bonnes permissions
touch /etc/traefik/acme.json
chmod 600 /etc/traefik/acme.json

Avec Docker, créez le fichier avant de lancer le conteneur :

Fenêtre de terminal
mkdir -p ~/traefik/certs
touch ~/traefik/certs/acme.json
chmod 600 ~/traefik/certs/acme.json

Une fois le resolver configuré, activez-le sur vos routers :

docker-compose.yml
services:
mon-app:
image: mon-image
labels:
- "traefik.enable=true"
- "traefik.http.routers.mon-app.rule=Host(`app.exemple.com`)"
- "traefik.http.routers.mon-app.entrypoints=websecure"
- "traefik.http.routers.mon-app.tls=true"
- "traefik.http.routers.mon-app.tls.certresolver=letsencrypt"

C'est la méthode la plus simple. Let's Encrypt vérifie que vous contrôlez le domaine en faisant une requête HTTP sur le port 80.

Cet enchaînement se déroule en quelques secondes et sans intervention de votre part. Le point de rupture le plus fréquent est l'étape 3 : la requête vient des serveurs de Let's Encrypt, donc depuis Internet, et doit atteindre le port 80 de votre machine. Un test local réussi ne prouve rien.

┌──────────────────────────────────────────────────────────────┐
│ HTTP-01 Challenge │
└──────────────────────────────────────────────────────────────┘
1. Traefik demande un certificat pour app.exemple.com
2. Let's Encrypt génère un token unique
3. Let's Encrypt fait une requête :
GET http://app.exemple.com/.well-known/acme-challenge/{token}
4. Traefik répond avec la preuve de contrôle
5. Let's Encrypt délivre le certificat

Les cinq étapes ci-dessous se suivent dans l'ordre. Gardez le port 80 ouvert même si vous redirigez tout vers HTTPS à l'étape 4 : la redirection renvoie les visiteurs, mais Traefik continue de répondre lui-même aux requêtes /.well-known/acme-challenge/ sur cet entrypoint.

  1. Configurer les entrypoints

    Vous avez besoin de deux entrypoints : un pour HTTP (port 80) et un pour HTTPS (port 443) :

    traefik.yaml
    entryPoints:
    web:
    address: ":80"
    websecure:
    address: ":443"
  2. Configurer le certificate resolver

    traefik.yaml
    certificatesResolvers:
    letsencrypt:
    acme:
    email: "admin@exemple.com"
    storage: /etc/traefik/acme.json
    httpChallenge:
    entryPoint: web
  3. Activer TLS sur vos services

    # docker-compose.yml - Service avec HTTPS
    services:
    whoami:
    image: traefik/whoami:v1.11.0@sha256:200689790a0a0ea48ca45992e0450bc26ccab5307375b41c84dfc4f2475937ab
    labels:
    - "traefik.enable=true"
    - "traefik.http.routers.whoami.rule=Host(`whoami.exemple.com`)"
    - "traefik.http.routers.whoami.entrypoints=websecure"
    - "traefik.http.routers.whoami.tls.certresolver=letsencrypt"
  4. Redirection HTTP vers HTTPS (optionnel mais recommandé)

    Ajoutez une redirection automatique dans les entrypoints :

    traefik.yaml
    entryPoints:
    web:
    address: ":80"
    http:
    redirections:
    entryPoint:
    to: websecure
    scheme: https
    permanent: true
  5. Vérifier le certificat

    Fenêtre de terminal
    # Attendre quelques secondes que le certificat soit généré
    curl -v https://whoami.exemple.com 2>&1 | grep -A5 "Server certificate"
    # Ou avec openssl
    echo | openssl s_client -connect whoami.exemple.com:443 -servername whoami.exemple.com 2>/dev/null | openssl x509 -noout -issuer -subject -dates

    Résultat attendu :

    issuer=C = US, O = Let's Encrypt, CN = R3
    subject=CN = whoami.exemple.com
    notBefore=Feb 17 12:00:00 2026 GMT
    notAfter=May 18 12:00:00 2026 GMT

Le DNS Challenge est nécessaire pour les certificats wildcard (ex: *.exemple.com) ou quand le port 80 n'est pas accessible depuis Internet.

Le DNS Challenge coûte plus cher en configuration : il faut un token d'API chez votre hébergeur DNS, donc un secret à protéger et à faire tourner. En contrepartie, il fonctionne dans des situations où le HTTP Challenge est impossible, notamment sur un service qui n'est jamais exposé à Internet. Le tableau ci-dessous résume les cas où le choix n'en est pas un.

SituationHTTP ChallengeDNS Challenge
Port 80 ouvert
Port 80 fermé (entreprise)
Certificat wildcard
Réseau privé (homelab)
Multi-domaines sur un certificat

Traefik supporte plus de 100 providers DNS. Voici les plus courants :

ProviderProvider nameVariables d'environnement
CloudflarecloudflareCF_API_EMAIL, CF_DNS_API_TOKEN
OVHovhOVH_ENDPOINT, OVH_APPLICATION_KEY, OVH_APPLICATION_SECRET, OVH_CONSUMER_KEY
AWS Route53route53AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION
Google Cloud DNSgcloudGCE_PROJECT, credentials file
Azure DNSazureAZURE_CLIENT_ID, AZURE_CLIENT_SECRET, AZURE_TENANT_ID, AZURE_SUBSCRIPTION_ID, AZURE_RESOURCE_GROUP
HetznerhetznerHETZNER_API_KEY
DigitalOceandigitaloceanDO_AUTH_TOKEN

La liste complète est disponible sur lego DNS providers.

Le token créé ici donne le droit de modifier vos enregistrements DNS : limitez-le à la zone concernée, comme indiqué à la première étape, et traitez-le comme un mot de passe d'administration.

  1. Créer un token API Cloudflare

    Dans Cloudflare Dashboard → My Profile → API Tokens → Create Token :

    • Template : Edit zone DNS
    • Zone Resources : Include → Specific zone → votre domaine
    • Copiez le token généré
  2. Configurer le resolver avec DNS Challenge

    traefik.yaml
    certificatesResolvers:
    letsencrypt-dns:
    acme:
    email: "admin@exemple.com"
    storage: /etc/traefik/acme.json
    dnsChallenge:
    provider: cloudflare
    propagation:
    delayBeforeChecks: 10s
    resolvers:
    - "1.1.1.1:53"
    - "8.8.8.8:53"
  3. Passer les variables d'environnement

    docker-compose.yml
    services:
    traefik:
    image: traefik:v3.7.6@sha256:21a3d83696379bac6434bb32e1dde0aff0e84ef2abd053ed3db87d3f45e749b2
    environment:
    - CF_API_EMAIL=votre-email@exemple.com
    - CF_DNS_API_TOKEN=votre-token-cloudflare
    # ...
  4. Configurer un certificat wildcard

    services:
    traefik:
    labels:
    - "traefik.http.routers.wildcard.tls.certresolver=letsencrypt-dns"
    - "traefik.http.routers.wildcard.tls.domains[0].main=exemple.com"
    - "traefik.http.routers.wildcard.tls.domains[0].sans=*.exemple.com"

OVH demande quatre valeurs au lieu d'un token unique, et sa propagation DNS est plus lente que celle de Cloudflare : le delayBeforeChecks passe ici à 30 secondes. Les trois droits accordés au token correspondent exactement au cycle de vie de l'enregistrement TXT de validation : le lire, le créer, le supprimer.

  1. Créer les clés API OVH

    Rendez-vous sur https://eu.api.ovh.com/createToken/ et créez un token avec les droits :

    • GET /domain/zone/*
    • POST /domain/zone/*
    • DELETE /domain/zone/*
  2. Configurer Traefik

    traefik.yaml
    certificatesResolvers:
    letsencrypt-dns:
    acme:
    email: "admin@exemple.com"
    storage: /etc/traefik/acme.json
    dnsChallenge:
    provider: ovh
    propagation:
    delayBeforeChecks: 30s # OVH peut être lent
  3. Variables d'environnement

    docker-compose.yml
    environment:
    - OVH_ENDPOINT=ovh-eu
    - OVH_APPLICATION_KEY=xxx
    - OVH_APPLICATION_SECRET=xxx
    - OVH_CONSUMER_KEY=xxx

Let's Encrypt met à disposition un environnement de test, dit staging, qui parle le même protocole mais délivre des certificats non reconnus par les navigateurs. Son intérêt est ailleurs : ses quotas sont bien plus larges que ceux de la production. Tant que votre configuration n'est pas validée, c'est le seul endroit où il faut se tromper.

Let's Encrypt impose des limites strictes en production :

LimiteValeur
Certificats par domaine50 / semaine
Échecs de validation5 / heure / compte
Nouveaux enregistrements10 / 3 heures / IP

Ajoutez le serveur staging dans votre configuration :

traefik.yaml
certificatesResolvers:
letsencrypt-staging:
acme:
email: "admin@exemple.com"
storage: /etc/traefik/acme-staging.json # Fichier séparé !
caServer: "https://acme-staging-v02.api.letsencrypt.org/directory"
httpChallenge:
entryPoint: web

Utilisez ce resolver pour vos tests :

- "traefik.http.routers.test.tls.certresolver=letsencrypt-staging"

Une fois que le staging fonctionne :

  1. Changez le resolver vers letsencrypt (production)
  2. Supprimez le fichier acme-staging.json
  3. Redémarrez Traefik
Fenêtre de terminal
rm /etc/traefik/acme-staging.json
docker compose restart traefik

Obtenir un certificat ne dit rien de la qualité de la connexion négociée ensuite. Ces réglages portent sur les versions de TLS acceptées, les algorithmes de chiffrement autorisés et le comportement face à un client qui n'annonce pas le nom du site demandé. Ils vivent tous dans la configuration dynamique, donc rechargeable sans redémarrage.

Définissez des profils TLS réutilisables pour contrôler les versions et cipher suites :

config/dynamic/tls.yaml
tls:
options:
# Profil moderne (TLS 1.3 uniquement)
modern:
minVersion: VersionTLS13
sniStrict: true
# Profil intermédiaire (compatibilité TLS 1.2)
intermediate:
minVersion: VersionTLS12
cipherSuites:
- TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
- TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
- TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
- TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
- TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305
- TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305

Appliquez un profil à un router :

# Docker labels
- "traefik.http.routers.secure-app.tls.options=modern@file"

Le SNI (Server Name Indication) permet d'héberger plusieurs certificats sur une même IP. Avec sniStrict: true, Traefik rejette les connexions qui ne fournissent pas de SNI valide :

tls:
options:
strict:
minVersion: VersionTLS12
sniStrict: true # Rejette les connexions sans SNI

HSTS (HTTP Strict Transport Security) est un en-tête qui ordonne au navigateur de ne plus jamais contacter votre domaine en clair pendant la durée indiquée. Deux options méritent réflexion avant activation. stsIncludeSubdomains étend la règle à tous les sous-domaines, y compris ceux qui n'ont pas encore de certificat. stsPreload sert à une inscription dans la liste embarquée des navigateurs, dont la sortie prend des mois : ne l'activez que si HTTPS fonctionne partout, durablement.

config/dynamic/middlewares.yaml
http:
middlewares:
hsts:
headers:
stsSeconds: 31536000 # 1 an
stsIncludeSubdomains: true
stsPreload: true
forceSTSHeader: true

Appliquez-le à vos routers :

- "traefik.http.routers.app.middlewares=hsts@file"

Un certificat Let's Encrypt vit 90 jours, ce qui rend le renouvellement automatique indispensable. Traefik s'en charge seul, mais deux points restent à votre charge : surveiller que le renouvellement a bien lieu, et savoir le forcer quand un certificat doit être remplacé avant terme, par exemple après l'ajout d'un sous-domaine.

Traefik renouvelle automatiquement les certificats 30 jours avant leur expiration. Vous n'avez rien à faire.

Le processus se déclenche au démarrage de Traefik et vérifie tous les certificats stockés dans acme.json.

Surveillez les certificats avec les métriques Prometheus :

traefik.yaml
metrics:
prometheus:
entryPoint: metrics
addServicesLabels: true

Métrique disponible : traefik_tls_certs_not_after_timestamp

Exemple d'alerte Alertmanager :

- alert: CertificateExpiringSoon
expr: (traefik_tls_certs_not_after_timestamp - time()) / 86400 < 14
for: 1h
labels:
severity: warning
annotations:
summary: "Certificat expire dans moins de 14 jours"

Si vous devez forcer un renouvellement :

Fenêtre de terminal
# Sauvegarder puis supprimer le certificat
cp /etc/traefik/acme.json /etc/traefik/acme.json.bak
# Supprimer uniquement le certificat concerné (avec jq)
jq 'del(.letsencrypt.Certificates[] | select(.domain.main == "exemple.com"))' \
/etc/traefik/acme.json > /tmp/acme.json && mv /tmp/acme.json /etc/traefik/acme.json
# Redémarrer Traefik
docker compose restart traefik

Toutes les configurations de ce guide sont regroupées dans un lab local. Il permet de vérifier le démarrage de Traefik, les permissions du fichier de stockage et le chargement de la configuration dynamique sans consommer de quota chez Let's Encrypt, puisqu'aucun certificat réel n'est demandé.

Fenêtre de terminal
cd ~/Projets/lab-traefik-tls-acme

Cette arborescence reprend la séparation vue plus haut : traefik.yaml porte la configuration statique, tout ce qui se trouve sous dynamic/ est rechargé à chaud, et certs/ isole le fichier sensible.

lab-traefik-tls-acme/
├── docker-compose.yml # Stack Docker
├── config/
│ ├── traefik.yaml # Configuration statique
│ └── dynamic/
│ ├── tls.yaml # Options TLS
│ └── middlewares.yaml # Middlewares HTTPS
└── certs/
└── acme.json # Stockage certificats (chmod 600)

Créez acme.json avant le premier docker compose up : lancé sur un chemin inexistant, Docker crée un répertoire à la place du fichier et Traefik refuse de démarrer. Le -k du dernier curl accepte le certificat auto-signé servi en local, et l'en-tête Host simule le nom de domaine attendu par le router.

Fenêtre de terminal
# Créer le fichier de certificats
touch certs/acme.json && chmod 600 certs/acme.json
# Démarrer avec certificats auto-signés (pour tests locaux)
docker compose up -d
# Vérifier les logs
docker compose logs -f traefik
# Tester
curl -k https://localhost:8443 -H "Host: whoami.localhost"

Pour inspecter finement un certificat servi (chaîne, SNI, expiration, version TLS négociée), le guide diagnostic TLS avec openssl détaille la lecture d'openssl s_client et des codes de vérification.

Symptôme : pas de certificat, erreur 526 ou certificat auto-signé affiché.

Vérifications :

Fenêtre de terminal
# 1. Vérifier les logs Traefik
docker compose logs traefik | grep -i acme
# 2. Vérifier que le port 80 est accessible
curl http://votre-domaine.com
# 3. Vérifier le DNS
dig +short votre-domaine.com A
# 4. Vérifier le fichier acme.json
cat /etc/traefik/acme.json | jq '.letsencrypt'

Solutions courantes :

ErreurCauseSolution
"acme: error validating"Port 80 bloquéOuvrir le firewall ou utiliser DNS Challenge
"too many certificates"Rate limit atteintAttendre 1 semaine ou utiliser staging
"DNS problem"DNS mal configuréVérifier les enregistrements A/AAAA

Vous avez atteint la limite Let's Encrypt. Options :

  1. Attendre : la limite se réinitialise après 7 jours
  2. Utiliser staging : pas de limite pour les tests
  3. Utiliser un wildcard : un seul certificat pour tous les sous-domaines

Avec le DNS Challenge, Let's Encrypt peut ne pas voir l'enregistrement TXT à temps.

# Augmenter le délai
certificatesResolvers:
letsencrypt-dns:
acme:
dnsChallenge:
provider: ovh
propagation:
delayBeforeChecks: 60s # Augmenter à 60s voire plus

Vous pouvez aussi spécifier des resolvers DNS rapides :

resolvers:
- "1.1.1.1:53" # Cloudflare
- "8.8.8.8:53" # Google

Si votre certificat est signé par "Fake LE" en production :

  1. Vérifiez que vous utilisez le bon resolver (pas -staging)
  2. Supprimez acme.json et redémarrez pour forcer un nouveau certificat
  3. Vérifiez les logs pour les erreurs ACME
  • Let's Encrypt fournit des certificats TLS gratuits et automatiques via le protocole ACME
  • HTTP Challenge est le plus simple (port 80 requis), DNS Challenge permet les wildcards
  • Toujours tester avec staging avant de passer en production pour éviter les rate limits
  • Le fichier acme.json doit avoir les permissions 600 (lecture/écriture propriétaire uniquement)
  • Traefik renouvelle automatiquement les certificats 30 jours avant expiration
  • Utilisez les TLS Options pour définir des profils de sécurité réutilisables

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracking. Un soutien, même symbolique, m'aide à couvrir l'hébergement et à garder ces ressources gratuites. Merci pour votre appui.

Le formulaire ne s'affiche pas ? Ouvrir Ko-fi dans un onglet.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn