Traefik’s Automatic TLS with ACME: Docker Labels & Production Pitfalls
Learn how Traefik can request, renew, and serve Let’s Encrypt certificates automatically using Docker labels, and discover the operational trade‑offs you need to watch out for in a real deployment.
19 Feb 2026, 15:08 UTC

Problem
Running a modern web stack often means exposing services on HTTPS. Manually managing certificates with certbot or a third‑party tool adds operational overhead and can become fragile when you add or remove services. Traefik, a popular reverse‑proxy, offers an out‑of‑the‑box ACME integration that can request, renew, and serve certificates automatically. This blog walks through the concrete steps to enable that feature in a Docker‑compose environment, explains the underlying challenge types, and highlights the trade‑offs you’ll encounter in production.
How Traefik’s ACME Works
Traefik’s ACME support is built around a certificate resolver. In the static configuration you declare a resolver and point it to an ACME server (by default Let’s Encrypt production). Traefik then handles the entire lifecycle: it submits a challenge, waits for the challenge to be satisfied, stores the resulting certificate, and keeps it up‑to‑date.
Three challenge types are available:
- HTTP‑01 – Traefik serves a token on
/.well-known/acme-challenge/via the HTTP entrypoint. Requires inbound port 80 to be reachable from the ACME server. - TLS‑ALPN‑01 – Traefik answers a TLS handshake on port 443 with a special ALPN protocol. Useful when port 80 is blocked.
- DNS‑01 – Traefik updates a TXT record on the DNS provider’s API. The only option that can issue wildcard certificates.
Certificates are stored in a JSON file (by convention acme.json). The file must be persisted across restarts and protected with chmod 600; otherwise Traefik will treat the data as missing and re‑issue certificates, potentially hitting Let’s Encrypt rate limits.
Configuring a Docker‑Compose Service
The following example demonstrates a minimal Traefik setup with the HTTP‑01 challenge and a single service that uses Docker labels to request a certificate.
# docker-compose.yml
version: "3.8"
services:
traefik:
image: traefik:v2.10
command: |
--api.insecure=true
--entrypoints.web.address=:80
--entrypoints.websecure.address=:443
--providers.docker=true
--certificatesresolvers.myresolver.acme.tlschallenge=true
--certificatesresolvers.myresolver.acme.email=[contact removed]
--certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./acme.json:/letsencrypt/acme.json
restart: unless-stopped
web:
image: nginx:alpine
labels:
- "traefik.http.routers.web.rule=Host(`example.com`)&PathPrefix(`/`)"
- "traefik.http.routers.web.entrypoints=websecure"
- "traefik.http.routers.web.tls=true"
- "traefik.http.routers.web.tls.certresolver=myresolver"
restart: unless-stopped
Key points:
- The
certificatesresolvers.myresolver.acme.tlschallenge=trueflag tells Traefik to use the TLS‑ALPN challenge. If you prefer HTTP‑01, replace that line with--certificatesresolvers.myresolver.acme.httpchallenge=trueand add--certificatesresolvers.myresolver.acme.httpchallenge.entrypoint=web. - Each router that should get a TLS certificate must include
traefik.http.routers.<name>.tls.certresolver=myresolverin its labels. - The
acme.jsonfile is stored on the host and mounted into the container. Make sure the file exists and has the right permissions before starting Traefik:touch acme.json && chmod 600 acme.json.
After bringing the stack up, Traefik will automatically request a certificate for example.com and serve it on HTTPS. You can verify the result with:
curl -vI https://example.com
Look for a 200 OK response and a Server: nginx header to confirm the traffic is routed correctly.
Operational Trade‑offs
While the automatic flow is convenient, several operational considerations can affect a production deployment:
File‑Based Persistence vs. Cluster Safety
Traefik stores the ACME state in a single JSON file. In a single‑node environment this is fine, but in a Kubernetes or Docker‑Swarm cluster with multiple Traefik replicas, concurrent writes can corrupt the file or cause duplicate requests. Solutions include:
- Run a single Traefik instance behind a load balancer and use a shared volume for
acme.json. - Switch to Traefik Enterprise’s
certificatesresolvers.myresolver.acme.stagingwith a distributed key‑value store (etcd, Consul) for state sharing. - Use an external certificate manager (e.g., cert-manager) and let Traefik consume the certificates via
tls.certresolver=external.
Rate Limits and Staging vs. Production
Let’s Encrypt enforces strict limits: 50 certificates per domain per week in production. A mis‑configured host rule that triggers many on‑demand requests can quickly exhaust these limits. To mitigate:
- During development point
caServerto the Let’s Encrypt staging endpoint:--certificatesresolvers.myresolver.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory. - Verify that certificates are indeed from staging by inspecting the issuer with
openssl s_client -connect example.com:443 -servername example.com– the issuer should beLet's Encrypt Authority X3for production orLet's Encrypt Test Authority X1for staging. - Never keep the staging server in production; otherwise you’ll never get a valid certificate for real users.
Challenge Type Suitability
HTTP‑01 is the simplest but requires port 80 to be open. In highly secure environments where port 80 is blocked, TLS‑ALPN is a better fit. DNS‑01, while requiring API credentials, is the only way to obtain wildcard certificates and is resilient to port restrictions. However, DNS‑01 providers differ in how they expose credentials; you must set environment variables like CF_API_EMAIL and CF_API_KEY for Cloudflare, or AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY for Route53.
Actionable Checklist
- Create
acme.jsonwithchmod 600and mount it into the Traefik container. - Define a
certificatesresolvers.<name>section in the static config, choosing the challenge type that fits your network constraints. - Add the
tls.certresolver=<name>label to every Docker service that needs HTTPS. - During development, point
caServerto the staging endpoint and verify the issuer withopenssl s_client. - In production, revert to the production endpoint, monitor
traefik.logfor renewal messages, and ensureacme.jsonis backed up. - Consider a shared volume or external cert manager if you run multiple Traefik instances.
By following these steps you can let Traefik handle TLS for you, freeing your team from manual certificate issuance while staying aware of the key operational boundaries.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.