SSL certificates on Traefik

Traefik obtains and renews certificates itself, like Caddy. The difference that catches people is the split between static configuration — read once at startup, where the ACME resolver is declared — and dynamic configuration, usually container labels, where a router says which resolver to use. Putting the resolver in the labels does nothing at all, silently.

# traefik.yml — static, read only at startup entryPoints: web: address: ":80" http: redirections: entryPoint: to: websecure scheme: https websecure: address: ":443" certificatesResolvers: le: acme: email: [email protected] storage: /data/acme.json httpChallenge: entryPoint: web

Then the router picks it up. With the Docker provider that is two labels on the service being published:

services: app: image: my/app labels: - "traefik.enable=true" - "traefik.http.routers.app.rule=Host(`example.com`)" - "traefik.http.routers.app.entrypoints=websecure" - "traefik.http.routers.app.tls.certresolver=le" traefik: image: traefik:v3 ports: ["80:80", "443:443"] volumes: - ./traefik.yml:/traefik.yml:ro - ./acme.json:/data/acme.json - /var/run/docker.sock:/var/run/docker.sock:ro
acme.json must be mode 0600 or Traefik refuses to start — touch acme.json && chmod 600 acme.json before the first run. And it must be a persisted volume: an ephemeral one re-issues every certificate on every restart, which reaches the Let's Encrypt rate limit fast and then locks you out for a week.

Using a certificate you already have

Drop the resolver and supply the files through the dynamic file provider instead. Traefik stops managing renewal entirely at that point — it is now your job.

# dynamic.yml, loaded via providers.file in the static config tls: certificates: - certFile: /certs/fullchain.pem keyFile: /certs/example.com.key stores: default: defaultCertificate: certFile: /certs/fullchain.pem keyFile: /certs/example.com.key

certFile is the fullchain — leaf first, then intermediates. Without defaultCertificate, any request whose SNI matches no router gets Traefik's built-in self-signed certificate rather than an error, which is why a wrong Host() rule looks like a certificate problem.

When it does not issue

Verify it from the outside

A container that starts cleanly can still be serving the default self-signed certificate. Check what Traefik actually presents:

openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null | openssl x509 -noout -subject -issuer -dates

An issuer of TRAEFIK DEFAULT CERT means no router matched the name. Or use the Domain SSL Checker, which performs the same handshake from this server and flags an incomplete chain explicitly.

Other platforms