Background & Why Use Traefik with DNS Challenge?
Managing SSL the traditional way while running dozens of containers on a single VPS? It’s a genuine nightmare. Every new service means another round of tedious setup: configuring a server block in Nginx, opening port 80, adding an A record, then sitting around waiting for Let’s Encrypt to validate via the HTTP-01 challenge. One new subdomain takes 10–15 minutes. Ten of them? There goes your entire afternoon.
Traefik v3 solves this problem in a completely different way. It continuously watches the Docker socket — when you start a new container with the right labels attached, Traefik automatically detects it and provisions SSL without you lifting a finger. Even better, by combining it with Cloudflare DNS Challenge, you get:
- Wildcard SSL certificates (*.yourdomain.com): a single certificate that covers every subdomain.
- No need to expose port 80 publicly — ideal for internal services that shouldn’t be accessible from the internet.
- Automatic renewal before expiration — zero manual intervention required.
I used Nginx Proxy Manager for a while, but switching to Traefik v3 felt like going from a manual transmission to an automatic — everything is fully automated thanks to Docker’s label system.
Installation & Prerequisites
Missing any one of these three things will leave you stuck halfway through, so get everything ready before you begin:
1. Cloudflare API Token
This token allows Traefik to create a temporary TXT record on DNS to prove domain ownership — Let’s Encrypt checks that record before issuing the certificate. Never use the Global API Key: if it leaks, your entire Cloudflare account could be compromised.
Go to My Profile > API Tokens > Create Token. Select the “Edit zone DNS” template, and scope the permissions to only the specific zone (domain) you need — don’t grant access to the entire account.
2. Directory Structure
I typically organize directories like this for easier management and backups:
mkdir -p ~/traefik/data
touch ~/traefik/data/acme.json
chmod 600 ~/traefik/data/acme.json
touch ~/traefik/docker-compose.yml
touch ~/traefik/data/traefik.yml
Note: The acme.json file stores your SSL certificates. It must have 600 permissions — otherwise, Traefik will refuse to start with the error “acme.json” should have permissions 600.
3. Point Your Domain to the VPS
Add an A record pointing *.yourdomain.com to your VPS IP in the Cloudflare dashboard. Important: disable the Proxy (the orange cloud icon) at this step. If you leave Proxy enabled, Traefik will receive Cloudflare’s IP instead of your VPS IP — and the cert request will fail immediately.
Traefik v3 Configuration in Detail
This section walks through each file one by one. Traefik v3 changed some syntax from v2 — particularly around entryPoints and certResolver — so don’t copy your old config wholesale without reviewing it first.
Step 1: Static Configuration File (traefik.yml)
This file is loaded once at startup and defines entrypoints, the Docker provider, and ACME configuration for automatic certificate provisioning.
api:
dashboard: true
debug: false
entryPoints:
web:
address: ":80"
http:
redirections:
entryPoint:
to: websecure
scheme: https
websecure:
address: ":443"
providers:
docker:
endpoint: "unix:///var/run/docker.sock"
exposedByDefault: false
certificatesResolvers:
cloudflare:
acme:
email: [email protected]
storage: /acme.json
dnsChallenge:
provider: cloudflare
resolvers:
- "1.1.1.1:53"
- "8.8.8.8:53"
Step 2: Docker Compose File (docker-compose.yml)
This file starts Traefik and mounts the three things it needs: the static config, the Docker socket (to read container labels), and acme.json (to store certificates once issued).
services:
traefik:
image: traefik:v3.0
container_name: traefik
restart: always
security_opt:
- no-new-privileges:true
networks:
- proxy
ports:
- 80:80
- 443:443
environment:
- CF_DNS_API_TOKEN=YOUR_CLOUDFLARE_API_TOKEN
volumes:
- /etc/localtime:/etc/localtime:ro
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./data/traefik.yml:/traefik.yml:ro
- ./data/acme.json:/acme.json
labels:
- "traefik.enable=true"
- "traefik.http.routers.dashboard.entrypoints=websecure"
- "traefik.http.routers.dashboard.rule=Host(`traefik.yourdomain.com`)"
- "traefik.http.routers.dashboard.service=api@internal"
- "traefik.http.routers.dashboard.tls.certresolver=cloudflare"
- "traefik.http.routers.dashboard.tls.domains[0].main=yourdomain.com"
- "traefik.http.routers.dashboard.tls.domains[0].sans=*.yourdomain.com"
networks:
proxy:
external: true
Create the network before running: docker network create proxy. This network is shared by every container you want to expose through Traefik — create it once, use it forever.
When debugging Docker API responses or inspecting Traefik’s dynamic config as JSON, I paste it into the formatter at toolcraft.app/en/tools/developer/json-formatter — much faster than installing a browser extension.
Step 3: Deploy a Test Application
To verify the system is working, deploy a lightweight whoami container:
services:
whoami:
image: traefik/whoami
container_name: test-app
networks:
- proxy
labels:
- "traefik.enable=true"
- "traefik.http.routers.whoami.entrypoints=websecure"
- "traefik.http.routers.whoami.rule=Host(`whoami.yourdomain.com`)"
- "traefik.http.routers.whoami.tls.certresolver=cloudflare"
Verification & Monitoring
After running docker compose up -d, check the logs right away:
docker logs -f traefik
When you see the line "Legitimate certificate retrieved for domain yourdomain.com", your certificate has been issued. Open https://whoami.yourdomain.com — a green HTTPS padlock with no warnings means you’re all set.
A few things to keep an eye on when monitoring:
- Dashboard: Visit
https://traefik.yourdomain.comto get a visual overview of running routers and services — extremely useful when debugging. - Rate Limit: Let’s Encrypt caps you at 50 certs per domain per week. Misconfiguring and restarting repeatedly can drain that quota in just a few hours. When testing, use the
stagingenvironment by addingcaServer: "https://acme-staging-v02.api.letsencrypt.org/directory"to theacmesection intraefik.yml. - Propagation: Cloudflare’s TXT records need anywhere from 30 seconds to a few minutes to propagate. Don’t restart the container right away if you don’t see a cert in the first minute or two — let Traefik handle it automatically.
After running this setup for several weeks, I haven’t had to touch SSL once. Adding a new service is just a matter of attaching the right labels — Traefik takes care of the rest. If you run into any issues along the way — especially around API Token permissions or DNS propagation — drop a comment below.

