Help:User journeys/Cloudflare in front of the edge

From Canasta Wiki

Journey info  ·  Platform: Docker Compose / Cloudflare  ·  Time: ~30 minutes

This journey puts a Canasta wiki on Docker Compose behind Cloudflare, with Cloudflare proxying the hostname and connecting to the origin in Full (strict) mode, so traffic is encrypted and the origin certificate validated end to end. The host's HAProxy edge terminates TLS at the origin and obtains its Let's Encrypt certificate by DNS validation through a Cloudflare API token, so issuance and renewal do not depend on Cloudflare passing HTTP challenges through to the origin. The edge also recovers each visitor's address from Cloudflare's header, so its rate limits and deny lists apply to visitors rather than to Cloudflare. It complements the reference pages Help:HAProxy edge and Help:Using Canasta with Cloudflare.

The examples use the zone example.com, the wiki hostname wiki.example.com, the instance ID mywiki and the host address 192.0.2.10. Substitute your own.

Prerequisites

  • A Linux host with Docker and the Canasta CLI installed (see Help:Installation), with ports 80 and 443 free, or already served by the host's edge.
  • A domain whose DNS is hosted on Cloudflare (here, the zone example.com).
  • Permission in the Cloudflare account to create API tokens and change the zone's DNS and SSL/TLS settings.
  • For an existing instance instead of a new one: a Docker Compose instance on the host.

Phase 1: Create the Cloudflare API token

The edge uses the token to create and remove the temporary _acme-challenge TXT records that prove control of the hostname to Let's Encrypt.

  1. In the Cloudflare dashboard, open My Profile → API Tokens and choose Create Token.
  2. Start from the Edit zone DNS template, or create a custom token with these permissions:
    • Zone → DNS → Edit
    • Zone → Zone → Read
  3. Under Zone Resources, include only the specific zone example.com.
  4. Create the token and copy it. Cloudflare shows it once.

Then copy the zone's Zone ID from the API section of the zone's Overview page. With the zone ID set, the edge keeps this token with this instance's certificates, so other instances on the host can use different tokens.

Treat the token as a password: anyone holding it can change the zone's DNS records.

Phase 2: Point DNS at the host

In the zone's DNS → Records, add a record for the wiki hostname, proxied (orange cloud):

Type Name Content Proxy status
A wiki 192.0.2.10 Proxied

Add an AAAA record as well if the host has an IPv6 address.

Leave the zone's SSL/TLS encryption mode alone for now. Until Let's Encrypt has issued the origin certificate (Phase 4), the edge serves a certificate from its own CA, which Full (strict) rejects.

Phase 3: Configure the instance

New instance

Create an env file, cloudflare.env, readable only by you:

TLS_MODE=acme-dns
ACME_DNS_PROVIDER=cloudflare
CLOUDFLARE_API_TOKEN=<token from Phase 1>
CLOUDFLARE_ZONE_ID=<zone ID from Phase 1>
TRUSTED_PROXIES=cloudflare
chmod 600 cloudflare.env
canasta create -i mywiki -w main -n wiki.example.com -e cloudflare.env

Every Compose instance is served by the edge, and the first one on the host starts it. The settings are copied into the instance's .env; delete cloudflare.env afterwards.

  • TLS_MODE=acme-dns has the edge obtain Let's Encrypt certificates by DNS validation.
  • CLOUDFLARE_API_TOKEN and CLOUDFLARE_ZONE_ID are the credentials from Phase 1.
  • TRUSTED_PROXIES=cloudflare has the edge (and Caddy) take the visitor's address from CF-Connecting-IP, on connections from Cloudflare's address ranges only.

Existing instance

For an existing instance:

canasta config set -i mywiki \
  CLOUDFLARE_API_TOKEN=<token> CLOUDFLARE_ZONE_ID=<zone ID> \
  TLS_MODE=acme-dns TRUSTED_PROXIES=cloudflare

The command restarts the instance, and the edge replaces its certificates with ones obtained by DNS validation. The token appears in your shell history; clear it from there afterwards.

Phase 4: Confirm the certificate, then switch to Full (strict)

Check that the edge holds a production Let's Encrypt certificate for the hostname:

canasta doctor -i mywiki

The HAProxy edge section should show:

HAProxy edge (mywiki):
  edge: running (...)
  member: yes
  acme.sh: running (...)
  wiki.example.com: Let's Encrypt production, renews ...

If the hostname instead shows "no Let's Encrypt certificate yet", see Troubleshooting before going on.

Then, in the Cloudflare dashboard, set SSL/TLS → Overview to Full (strict). Leave Always Use HTTPS on or off as you prefer: the edge redirects HTTP to HTTPS itself.

Phase 5: Lock the origin to Cloudflare

With DNS validation, nothing outside Cloudflare needs to reach the host on 80 or 443. In your provider's firewall or security group, allow inbound 80/TCP and 443/TCP only from Cloudflare's published address ranges, and 22/TCP from the addresses you administer from. Use the provider's firewall rather than ufw or firewalld: ports Docker publishes bypass host firewall rules (see Host firewall).

Phase 6: Validation

Cloudflare reaches the origin in Full (strict): from any machine,

curl -sI https://wiki.example.com/ | head -1

Expect HTTP/2 200 or a redirect to the main page, not 526 or 525.

The origin serves a publicly trusted certificate: on the host, bypassing Cloudflare,

curl -sI --resolve wiki.example.com:443:127.0.0.1 https://wiki.example.com/ | head -1

Expect a 200 or a redirect. A certificate error means the edge is still serving its own CA's certificate. canasta doctor -i mywiki also checks this, in its Origin TLS section.

The edge sees the visitor's address: load the wiki in a browser, then on the host:

docker logs --tail 20 canasta-edge

Your request's line shows your own public address, not one of Cloudflare's.

Cleanup / teardown

Delete the instance. If it was the last instance on the host, the edge stops too:

canasta delete -i mywiki -y

Then:

  1. In Cloudflare, delete the API token (My Profile → API Tokens), then the wiki's DNS record.
  2. If you changed the zone's SSL/TLS mode only for this wiki, set it back.
  3. Remove the firewall rules from Phase 5.
  4. Optionally, once no Compose instance remains on the host, remove the edge's directory, which holds its Let's Encrypt account, certificates and CA: /etc/canasta/edge if you run canasta as root, otherwise ~/.config/canasta/edge. A later instance creates a new one.

Production considerations

  • Token scope. Restrict the token to the one zone, and add Cloudflare's client IP filtering for the token if the host's address is fixed.
  • Several instances on one host can each use a different token, as long as every acme-dns instance on the host sets CLOUDFLARE_ZONE_ID. The edge refuses a configuration where instances with different tokens leave it unset.
  • Every hostname in the zone. Each wiki, media hostname and Caddyfile.global site of the instance gets its own certificate by DNS validation, so each needs to be in a zone the token can edit.
  • Rate limits. With TRUSTED_PROXIES=cloudflare set, each visitor has its own allowance. Tune CANASTA_EDGE_RATE_DYNAMIC and CANASTA_EDGE_RATE_DEFAULT as described in Rate limits and connection caps.
  • A Cloudflare Origin CA certificate is an alternative to Let's Encrypt in Full (strict): place the certificate and key in the instance's config/certs/ and set TLS_MODE=custom (see Certificates). It is trusted only by Cloudflare, so the origin can no longer be served without Cloudflare in front.
  • Testing. On a hostname you create and delete repeatedly, add CANASTA_STAGING_CERTS=true to the env file to avoid Let's Encrypt's rate limits, and use Full rather than Full (strict), since staging certificates are not publicly trusted.
  • Gitops. On a gitops instance, CLOUDFLARE_API_TOKEN is stored encrypted with the other secrets, and TLS_MODE, TRUSTED_PROXIES and CLOUDFLARE_ZONE_ID can differ per host.

Troubleshooting

  • canasta doctor says "no Let's Encrypt certificate yet". The start that tried printed Let's Encrypt's reason in a message beginning "Let's Encrypt did not issue a certificate for wiki.example.com". The usual causes are a token without DNS → Edit on the zone, or a CLOUDFLARE_ZONE_ID for a different zone. Fix the setting with canasta config set, which restarts the instance and retries.
  • 526 (Invalid SSL certificate). Cloudflare is in Full (strict) and the edge serves a certificate Cloudflare does not trust: its own CA's, while Let's Encrypt has not issued yet, or a staging certificate. Set Full until canasta doctor shows a production certificate.
  • 525 (SSL handshake failed). Cloudflare cannot complete TLS with the origin. Check that port 443 is open to Cloudflare's ranges, and that the edge is running (canasta doctor -i mywiki).
  • Redirect loop. The zone's SSL/TLS mode is Flexible, which connects to the origin over HTTP, and the edge redirects every HTTP request to HTTPS. Use Full (strict). Flexible works only with the instance served over plain HTTP; see Behind a CDN.
  • "use acme-dns with different Cloudflare tokens". Another acme-dns instance on the host uses a different token and one of them has no CLOUDFLARE_ZONE_ID. Set it on each.
  • Visitors get 429, or the edge log shows Cloudflare addresses. TRUSTED_PROXIES=cloudflare is not set on the instance, so the edge counts every visitor arriving through one Cloudflare address together. Set it.