Traefik + Cloudflare: Fix 526 SSL Error by Switching to DNS Challenge

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:Read
  • Zone: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.

comments powered by Disqus