Skip to content

Certificates and Cloudflare TLS

The origin gets real Let’s Encrypt certificates via the DNS-01 challenge: Traefik proves control of the domain by writing a TXT record through the Cloudflare API. No inbound port 80 needed.

Dashboard → My Profile → API Tokens → Create Token:

  1. Template Edit zone DNS (Zone · DNS · Edit); add Zone · Zone · Read.
  2. Zone Resources: include each zone you’ll issue certificates for.
  3. Client IP Address Filtering: your server’s IP, so the token only works from there.
  4. Copy it once. Write it on the server without it touching the screen or your shell history:
Terminal window
cd /opt/vps-infra/starter
read -rsp 'Cloudflare token: ' T && printf '%s' "$T" > secrets/cf_dns_api_token.txt && unset T; echo
chmod 600 secrets/cf_dns_api_token.txt
curl -s https://api.cloudflare.com/client/v4/user/tokens/verify \
-H "Authorization: Bearer $(cat secrets/cf_dns_api_token.txt)" | grep -o '"status":"[a-z]*"'

Traefik reads it as CF_DNS_API_TOKEN_FILE=/run/secrets/cf_dns_api_token.

Staging first — and why you can’t test it through Cloudflare

Section titled “Staging first — and why you can’t test it through Cloudflare”

Cloudflare’s Full (strict) mode requires an origin certificate from a public CA (or Cloudflare’s Origin CA). Let’s Encrypt staging certificates are not publicly trusted, so through Cloudflare they’d show visitors a 526. Test staging on the server itself:

Terminal window
echo | openssl s_client -connect 127.0.0.1:443 -servername whoami.jonathandobson.com 2>/dev/null \
| openssl x509 -noout -issuer -enddate
# issuer=… CN=(STAGING) …

This also proved the _FILE token setting worked — something the Traefik docs don’t cover (it comes from its ACME library).

  1. Set ACME_CA_SERVER=https://acme-v02.api.letsencrypt.org/directory in .env.
  2. Stop Traefik and move the staging store aside — rename, don’t delete: docker run --rm -v traefik-acme:/data alpine:latest mv /data/acme.json /data/acme.staging.json
  3. Start Traefik; certificates arrive in under a minute.
  4. Verify the chain is trusted, without -k: curl -s -o /dev/null -w '%{ssl_verify_result}\n' --resolve <host>:443:127.0.0.1 https://<host>/ — 0 means verified.

The starter’s .env described one wildcard certificate, but nothing actually requested it: each hostname gets its own certificate. That’s fine — just don’t expect a wildcard in the store.

Set every zone to Full (strict). Cloudflare’s newer Automatic mode is safe — it scans about once a month, raises the mode gradually (starting at 1% of traffic) and never lowers it — but until it reaches Full (strict) the origin certificate isn’t verified. Setting it yourself is immediate.