Traefik uses HTTP-01 ACME challenge by default. The way it works is: Let’s Encrypt sends a request to http://yourdomain/.well-known/acme-challenge/<token> and checks that Traefik responds with the right token. If it does, cert issued.
The problem is Cloudflare. When the orange cloud proxy is on, Let’s Encrypt’s request hits Cloudflare’s servers, not your origin. Traefik placed the challenge token on the origin. Cloudflare has no idea it’s there. It returns a 404. Let’s Encrypt fails the validation. No cert.
Traefik logs:
Unable to obtain ACME certificate for domains "yourdomain.example.com":
error: 403 :: unauthorized :: Invalid response from
https://yourdomain.example.com/.well-known/acme-challenge/xxxx [2606:4700:...] 404
That IP in brackets is Cloudflare’s. Let’s Encrypt never reached the origin.
The reason existing domains on the same setup were fine: their certs were already in acme.json from before Cloudflare proxy was turned on. Traefik just served the cached certs. New domains had no cached cert so Traefik tried to obtain one, failed, and Cloudflare had nothing to proxy a valid SSL handshake against.
Why HTTP-01 Cannot Work Here
HTTP-01 requires Let’s Encrypt to reach your actual server on port 80. If anything sits in front of it, the challenge either gets cached, rewritten, or 404’d. Cloudflare does all three depending on caching rules.
The fix is to use a challenge method that does not require Let’s Encrypt to reach your server at all: DNS-01.
The Solution
DNS-01 proves domain ownership by placing a TXT record in DNS. Let’s Encrypt checks the DNS record directly, nothing goes through Cloudflare proxy, and the cert gets issued regardless of what’s in front of your server.
Traefik has a built-in Cloudflare provider. You give it an API token with DNS edit permission and it creates and removes the TXT records itself.
1. Create a Cloudflare API Token
In the Cloudflare dashboard: My Profile > API Tokens > Create Token.
Use the “Edit zone DNS” template. Set Zone Resources to the specific domain you want to cover. The token needs:
Zone:Zone:ReadZone:DNS:Edit
This is a different token from a cache purge token. Do not reuse one without checking its permissions.
2. Update traefik.yml
Replace httpChallenge with dnsChallenge:
certificatesResolvers:
myresolver:
acme:
email: [email protected]
storage: /traefik/data/acme.json
dnsChallenge:
provider: cloudflare
resolvers:
- "1.1.1.1:53"
- "8.8.8.8:53"
The resolvers lines tell Traefik to use Cloudflare’s DNS servers when verifying the TXT record. This matters because your local DNS resolver might have cached the old state.
3. Pass the API Token to the Traefik Container
Create a .env file next to your Traefik docker-compose.yml:
CLOUDFLARE_DNS_API_TOKEN=your_token_here
Lock it down:
chmod 600 .env
Then in docker-compose.yml, reference it via env_file:
services:
traefik:
image: traefik:v2.3
env_file:
- .env
volumes:
- ./data/traefik.yml:/traefik.yml
- ./data/acme.json:/traefik/data/acme.json
- /var/run/docker.sock:/var/run/docker.sock
Restart Traefik:
docker compose down && docker compose up -d
Gotchas
The env var name is CLOUDFLARE_DNS_API_TOKEN, not CF_DNS_API_TOKEN. Traefik uses the lego library under the hood, which uses the longer name. Using the wrong name gives an error about missing credentials that lists both options, which makes it look like either would work. Neither works with the wrong name.
Do not use environment: with variable substitution in docker-compose.yml. This pattern is tempting:
environment:
- CLOUDFLARE_DNS_API_TOKEN=${CLOUDFLARE_DNS_API_TOKEN}
It works if your shell has the variable set when you run docker compose up. In practice you will forget, or it will be blank, and Traefik starts with an empty token. The env_file approach is more reliable because docker compose reads the file itself at startup, no shell environment required.
Cert issuance takes longer with DNS-01. Traefik has to create a DNS TXT record and wait for it to propagate before Let’s Encrypt can verify it. Usually 30 to 60 seconds rather than a few seconds for HTTP-01. The Traefik logs will show progress. If it is taking more than 2 minutes check the token permissions first.
End Result
Once this is in place, any new subdomain added to Traefik gets a cert automatically without touching Cloudflare proxy settings. The orange cloud can stay on.
