Traefik ACME: Why DNS‑01 Is the Safe Bet for Wildcard Certs
DNS‑01 challenges let Traefik issue wildcard certificates without exposing ports 80/443. This post shows a complete static config, Kubernetes IngressRouteTLS wiring, and a verification checklist to avoid Let’s Encrypt rate limits.
07 Aug 2026, 21:09 UTC

The problem: wildcard certificates without the rate‑limit headache
When you need a single certificate for *.example.com you have two choices in Traefik: use the HTTP‑01/TLS‑ALPN‑01 challenges on ports 80/443, or switch to DNS‑01. The first two require Traefik to be directly reachable on those ports. In many environments — cloud load balancers that terminate TLS, firewalls that block inbound 80/443, or clusters behind NAT — that simply isn’t true. The result is failed challenges, repeated retries, and a quick trip to Let’s Encrypt’s 50‑certificates‑per‑week limit.
DNS‑01 solves the reachability problem because the ACME server only asks you to create a TXT record in your DNS zone. Traefik can run anywhere as long as it has API credentials for a supported DNS provider. The trade‑off is operational: you must manage those credentials and verify that your provider is in the lego library that Traefik bundles.
Choosing the challenge type in static configuration
Traefik’s ACME client lives in the static configuration (file or CLI flags) under certificatesResolvers.<name>.acme. A minimal DNS‑01 resolver for Cloudflare looks like this:
certificatesResolvers:
cloudflare-dns:
acme:
email: "[contact removed]"
storage: "/letsencrypt/acme.json"
caServer: "https://acme-staging-v02.api.letsencrypt.org/directory"
dnsChallenge:
provider: "cloudflare"
# The provider reads CF_API_EMAIL and CF_API_KEY from the environment
Key points:
- storage must point to a persistent volume (PVC in Kubernetes, NFS mount in Swarm). Without it, every restart creates a new
acme.jsonand you burn rate limits. - caServer set to the staging directory lets you iterate safely; swap to the production directory once the flow works.
- The DNS provider reads credentials from environment variables — never hard‑code secrets in the config file.
Wiring the resolver to a wildcard route (Kubernetes example)
In Traefik v3 the CRD IngressRouteTLS references the resolver by name:
apiVersion: traefik.io/v1alpha1
kind: IngressRouteTLS
metadata:
name: wildcard-example
spec:
entryPoints:
- websecure
routes:
- match: Host(`*.example.com`)
kind: Rule
services:
- name: my-service
port: 80
tls:
certResolver: cloudflare-dns
If the resolver name is misspelled, Traefik silently falls back to its self‑signed default certificate — browsers will reject it. A quick kubectl get ingressroutetls wildcard-example -o yaml confirms the certResolver field matches the static config.
Verification checklist — did it actually work?
- Staging certificate appears: after Traefik starts, check the volume for
acme.json. It should contain aCertificatesarray with adomainobject whosecertificatefield is a base64‑encoded PEM andnotAfteris ~90 days out. - Log evidence: look for lines like
legobd: certificate obtainedand latercertificate renewed. Noerrorentries means the DNS challenge succeeded. - Prometheus metric: query
traefik_cert_not_after_timestamp_seconds{resolver="cloudflare-dns"}. The value should be roughlynow() + 7776000(90 days). - Restart test: delete the pod (or
docker compose restart). On restart you should seecertificate renewedonly if the cert is near expiry; otherwise the same certificate is reused — no new issuance in the logs. - Wildcard SAN check: run
openssl s_client -connect example.com:443 -servername foo.example.comand verify the certificate’s Subject Alternative Names include*.example.com.
Trade‑off: DNS‑01 isn’t free
DNS‑01 requires a provider that lego supports. As of Traefik v3.0 the list includes Cloudflare, Route 53, Azure DNS, Google Cloud DNS, and many others — but not every registrar. If your DNS host isn’t on the list you’ll need to delegate the zone to a supported provider or run a custom ACME client outside Traefik.
Credentials management is another concern. The API token must have permission to create and delete TXT records. Store it in a Kubernetes Secret (or Docker secret) and mount it as environment variables; never bake it into an image.
Finally, the acme.json file holds private keys in plaintext JSON. Set file permissions to 600 and encrypt the underlying volume at rest. Do not commit this file to version control.
Actionable next step
Spin up a test namespace, point a subdomain (e.g., test.example.com) to your Traefik ingress IP, and deploy the static config with the staging CA. Run the verification checklist above. Once the staging certificate renews cleanly after a pod restart, promote the caServer to the production directory and add the Prometheus alert on traefik_cert_not_after_timestamp_seconds (e.g., fire when < 30 days remain). That gives you a production‑grade, wildcard‑ready TLS pipeline without hitting Let’s Encrypt rate limits.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.