Why I Worked on This
I run several self-hosted services behind Traefik on my home network. For months, I used Cloudflare's DNS proxy mode (the orange cloud) because it hides my home IP and provides DDoS protection. But I hit a problem: Traefik's default HTTP-01 challenge for Let's Encrypt certificates doesn't work when Cloudflare proxies the traffic.
The HTTP-01 challenge requires Let's Encrypt to directly reach my server on port 80 to verify domain ownership. With Cloudflare in proxy mode, that connection gets intercepted. Let's Encrypt sees Cloudflare's IP, not mine, and the validation fails.
I needed valid SSL certificates without exposing my real IP or switching Cloudflare to DNS-only mode. I also wanted to see real visitor IPs in my logs instead of Cloudflare's proxy addresses.
My Real Setup
I run Traefik v3 in a Docker container on Proxmox. My domain DNS is managed through Cloudflare with proxy mode enabled. All incoming traffic hits Cloudflare first, then routes to my home server.
My Traefik setup uses:
- Docker Compose for container management
- A dedicated Docker network called
frontend - Let's Encrypt for SSL certificates
- Cloudflare API for DNS validation
I keep configuration in traefik.yml and secrets in a .env file that Docker Compose reads.
What Worked
Switching to DNS-01 Challenge
Instead of HTTP-01, I configured Traefik to use the DNS-01 challenge. This method proves domain ownership by creating a temporary TXT record in my DNS zone. Let's Encrypt queries Cloudflare's DNS servers directly—no need to reach my home IP.
I generated a Cloudflare API token with Zone / DNS / Edit permissions for my domain. I stored it in a .env file:
[email protected]
CF_DNS_API_TOKEN=my-generated-token-here
In my docker-compose.yml, I added these environment variables to the Traefik service:
environment:
- CF_API_EMAIL=${CF_API_EMAIL}
- CF_DNS_API_TOKEN=${CF_DNS_API_TOKEN}
Then I updated traefik.yml to use the DNS challenge:
certificatesResolvers:
letsencrypt:
acme:
email: [email protected]
storage: /certs/acme.json
caServer: https://acme-v02.api.letsencrypt.org/directory
dnsChallenge:
provider: cloudflare
delayBeforeCheck: 10
I set delayBeforeCheck to 10 seconds because DNS propagation isn't instant. Without this delay, Let's Encrypt sometimes queried before the TXT record was visible.
After recreating the container with docker compose up -d --force-recreate, Traefik successfully requested and renewed certificates through Cloudflare's DNS API.
Restoring Real Visitor IPs
With Cloudflare proxy mode enabled, all requests appear to come from Cloudflare's IP ranges. My application logs showed Cloudflare addresses instead of actual visitors.
Cloudflare sends the real visitor IP in the CF-Connecting-IP and X-Forwarded-For headers. I needed Traefik to trust these headers and pass them correctly to my backend services.
I added Cloudflare's IP ranges to Traefik's trusted proxy list. In traefik.yml, I configured the websecure entrypoint:
entryPoints:
websecure:
address: ":443"
forwardedHeaders:
trustedIPs:
- 173.245.48.0/20
- 103.21.244.0/22
- 103.22.200.0/22
- 103.31.4.0/22
- 141.101.64.0/18
- 108.162.192.0/18
- 190.93.240.0/20
- 188.114.96.0/20
- 197.234.240.0/22
- 198.41.128.0/17
- 162.158.0.0/15
- 104.16.0.0/13
- 104.24.0.0/14
- 172.64.0.0/13
- 131.0.72.0/22
- 2400:cb00::/32
- 2606:4700::/32
- 2803:f800::/32
- 2405:b500::/32
- 2405:8100::/32
- 2a06:98c0::/29
- 2c0f:f248::/32
I got this list from Cloudflare's official documentation. These ranges change occasionally, so I check them when Traefik updates or if IP logging looks wrong.
After this change, my application logs showed real visitor IPs instead of Cloudflare proxies. Rate limiting and geolocation features started working correctly.
What Didn't Work
First Attempt with HTTP-01
I initially tried keeping the HTTP-01 challenge with Cloudflare in proxy mode. I thought maybe Cloudflare would pass the validation request through. It didn't. Let's Encrypt's validation failed every time because it couldn't reach the /.well-known/acme-challenge/ path on my actual server.
I wasted time trying to configure Cloudflare page rules to bypass the proxy for that specific path. Even when I got the rule working, Let's Encrypt still failed because the SSL handshake itself was already proxied.
Forgetting the Delay
When I first set up DNS-01, I didn't include delayBeforeCheck. Traefik would create the TXT record via Cloudflare's API, then immediately tell Let's Encrypt to validate. Sometimes it worked, sometimes it didn't. The failures were random and frustrating.
I checked Cloudflare's DNS propagation manually with dig and noticed the TXT record took a few seconds to appear on all their nameservers. Adding a 10-second delay fixed the intermittent failures.
Wrong IP Ranges
I initially copied Cloudflare's IPv4 ranges but forgot the IPv6 ranges. Some visitors on IPv6 networks still showed Cloudflare IPs in my logs. I only noticed this when testing from my phone on mobile data, which uses IPv6.
I added the full list of both IPv4 and IPv6 ranges, and the problem disappeared.
Key Takeaways
DNS-01 challenges work reliably with Cloudflare proxy mode enabled. The validation happens entirely through DNS queries, so Let's Encrypt never needs to reach my home IP directly.
Cloudflare's API token only needs Zone / DNS / Edit permissions. I initially created a token with broader permissions, which was unnecessary and less secure.
The delayBeforeCheck setting matters. DNS propagation isn't instant, even with Cloudflare. A 10-second delay prevents random validation failures.
Trusting Cloudflare's IP ranges in Traefik is required to see real visitor IPs. Without this, all traffic appears to come from Cloudflare's proxies, breaking rate limiting and geolocation.
Cloudflare's IP ranges change over time. I check their documentation occasionally and update my Traefik config when needed. This isn't frequent, but ignoring it will eventually cause problems.
This setup lets me keep Cloudflare's DDoS protection and IP hiding while still getting valid SSL certificates and accurate visitor logs. It works consistently and renews certificates automatically without exposing my home network.