Backend / Networking / 20_cloudflare.md

Cloudflare in front of your origin

Updated 5 interview angles 7 min read source
On this page11
  1. The mechanism: an anycast reverse proxy chosen in DNS
  2. TLS: the mode is a choice about the second hop
  3. Caching: what the edge keeps
  4. Getting the real client IP at the origin
  5. Locking the origin
  6. WAF, rate limits, DDoS
  7. A Matrix homeserver behind Cloudflare
  8. Where it breaks in production
  9. The trade not taken: a cloud provider’s CDN and WAF
  10. Related
  11. Interview angle

Cloudflare in front of your origin

Article.

Verified 2026-10

Verified 2026-10-15 against Cloudflare’s developer docs; the header and address runs are against this site, which is served by Cloudflare Pages.

Cloudflare sits between users and your servers as a reverse proxy: DNS, caching, TLS, a web application firewall and DDoS absorption, from one anycast network.

Turning it on takes a click; the interview question is what changes for the origin once traffic arrives through someone else’s proxy, because most Cloudflare incidents are the origin misunderstanding that: redirect loops, lost client IPs, an origin still reachable directly, and ports that are silently not proxied.

The mechanism: an anycast reverse proxy chosen in DNS

Each DNS record is either proxied (the “orange cloud”) or DNS-only (grey). A proxied name resolves to Cloudflare’s addresses, not yours; the nearest Cloudflare location terminates the connection and opens its own to your origin.

text
user ── TLS ──► Cloudflare edge (anycast, nearest city)
                  │ cache · WAF · rate limits · DDoS
                  └── TLS (mode-dependent) ──► your origin

Seeing it from outside

Cloudflare publishes the ranges it serves from, and a proxied name resolves inside them:

python
v4 = curl("https://www.cloudflare.com/ips-v4").split()
v6 = curl("https://www.cloudflare.com/ips-v6").split()
nets = [ipaddress.ip_network(n) for n in v4 + v6]
addrs = {ai[4][0] for ai in socket.getaddrinfo(host, 443)}
inside = sum(any(ipaddress.ip_address(a) in n for n in nets) for a in addrs)
text
Cloudflare publishes 15 IPv4 and 7 IPv6 ranges, e.g. ['173.245.48.0/20', ...]
stackbook.pages.dev: 2 addresses, 2 inside Cloudflare's ranges
www.cloudflare.com: 2 addresses, 2 inside Cloudflare's ranges
pypi.org: 4 addresses, 0 inside Cloudflare's ranges

Responses carry the proxy’s fingerprints:

text
Server: cloudflare
CF-Ray: a4aff5965ff3babc-VIE          # request ID plus the edge location (Vienna)
CF-Cache-Status: HIT                  # served from the edge cache
alt-svc: h3=":443"; ma=86400          # HTTP/3 offered by the edge

CF-Ray is the first thing to ask for in a support case or to log at the origin: it ties a user’s error page to one request at one edge.

TLS: the mode is a choice about the second hop

Mode Edge to origin Verdict
Flexible plain HTTP a trap: looks secure to users, is not
Full HTTPS, certificate not checked encrypted, not authenticated
Full (strict) HTTPS, certificate validated the one to use

Flexible causes the classic redirect loop: the origin redirects HTTP to HTTPS, Cloudflare fetches it over HTTP again, forever. Full (strict) with a real certificate, or a free Origin CA certificate that only Cloudflare trusts, encrypts and authenticates both hops.

Caching: what the edge keeps

By default Cloudflare caches by file extension (images, CSS, JavaScript, fonts) and does not cache HTML or JSON. It also skips anything sent with Cache-Control: private, no-store, no-cache or max-age=0, anything with a Set-Cookie header, and every non-GET request.

Caching dynamic pages needs a cache rule plus correct Cache-Control from the origin, and the origin’s headers still decide how long (HTTP semantics and caching).

This site shows a trade worth knowing. Even its content-hashed stylesheet (Base.a-o2CiLe.css) is served with Cache-Control: public, max-age=0, must-revalidate, on purpose. Cloudflare Pages appends every matching _headers rule instead of letting the most specific win, so a /* policy plus a /_astro/* override produced one malformed header:

text
Cache-Control: public, max-age=0, must-revalidate, public, max-age=31536000, immutable

Browsers honour the first max-age, so the override bought nothing. The site keeps one rule: nine small hashed assets revalidate with cheap 304s, and in exchange a deploy is visible at once and a deleted page stops answering instead of being served from the edge for an hour.

Getting the real client IP at the origin

Behind the proxy, every request comes from a Cloudflare address. The client’s address is in CF-Connecting-IP (and appended to X-Forwarded-For). Trust it only from Cloudflare’s ranges, or anyone who reaches the origin directly can claim any IP:

nginx
# /etc/nginx/conf.d/cloudflare-realip.conf, generated from ips-v4 and ips-v6
set_real_ip_from 173.245.48.0/20;
set_real_ip_from 103.21.244.0/22;
# ... the other published ranges
real_ip_header CF-Connecting-IP;

Rate limits, audit logs and geo rules at the origin all depend on this; without it, every client looks like a handful of Cloudflare addresses.

Locking the origin

The proxy protects nothing if the origin answers on its own IP. Origins leak through old DNS records, mail records on the same server, certificate transparency logs and internet-wide scans. Three ways to close the door, strongest last:

  • Firewall to Cloudflare’s ranges on 80 and 443, refreshed when the list changes.
  • Authenticated Origin Pulls: Cloudflare presents a client certificate, and the origin requires it (mTLS).
  • Cloudflare Tunnel: cloudflared on the origin dials out to Cloudflare, so the origin needs no inbound ports at all.

WAF, rate limits, DDoS

  • DDoS protection at the network and HTTP layers is on for every plan; the anycast network absorbs volume your origin never sees.
  • WAF rules: managed rule sets for common attacks (paid plans), plus custom rules on any request field.
  • Rate-limiting rules at the edge stop abuse before it costs origin capacity; keep application limits too, for per-user and per-API-key logic the edge cannot see.

A Matrix homeserver behind Cloudflare

Two facts decide whether it works:

  • Only certain ports are proxied. For HTTPS: 443, 2053, 2083, 2087, 2096 and 8443. Matrix federation’s default 8448 is not among them, so a proxied name breaks federation unless discovery points elsewhere: serve /.well-known/matrix/server returning {"m.server": "matrix.example.org:443"} and let federation share 443 (Matrix and Synapse).
  • Requests time out after 125 seconds (error 524), adjustable only on Enterprise. A 30-second /sync long poll fits; long media uploads and admin exports may not, and belong on a DNS-only name.

WebSockets pass through the proxy on all plans; TURN does not (it is UDP and not HTTP), so a coturn server must be DNS-only (STUN, TURN and coturn).

Where it breaks in production

  • Redirect loops from Flexible mode plus an origin HTTPS redirect, or “Always Use HTTPS” plus an origin redirect to HTTP.
  • Every user has the same IP in logs and rate limiters: the real-IP module is missing or trusts the header from everywhere.
  • The origin is attacked directly because its IP was never hidden or the firewall was never closed.
  • The 52x family: 521 the origin refused the connection, 522 it timed out connecting, 524 it accepted but did not answer in 125 s, 525 and 526 TLS to the origin failed or its certificate is invalid under Full (strict).
  • Stale content after a deploy when HTML was cached by a rule without purging, or personal pages cached when a rule ignored Set-Cookie.
  • Federation silently broken because 8448 is not proxied.

The trade not taken: a cloud provider’s CDN and WAF

The alternative on AWS is CloudFront plus AWS WAF plus Shield, with Route 53 for DNS. It keeps everything in one account and IAM, integrates with S3 and ALB origins natively, and bills per request and per rule. Cloudflare bundles DNS, CDN, TLS, WAF and DDoS protection in one product, with unmetered DDoS mitigation even on the free plan and a far simpler setup, at the cost of a second vendor and an origin that must be locked to it.

For a small team with a public web product, Cloudflare is usually the shorter path; for a stack deep in AWS with private origins, CloudFront is.

Interview angle 5

  • “What does Cloudflare do when you proxy a domain?” - the name resolves to Cloudflare’s anycast addresses; the nearest edge terminates TLS, applies cache, WAF, rate limits and DDoS filtering, then connects to your origin.
    • Follow-up: which SSL mode? Full (strict), with a real or Origin CA certificate. Flexible sends plain HTTP to the origin and causes redirect loops.
  • “Your app’s logs show all requests from a few IPs.” - they are Cloudflare’s. Restore the client IP from CF-Connecting-IP with set_real_ip_from limited to Cloudflare’s published ranges.
    • Follow-up: why limited? Otherwise a request straight to the origin can claim any client IP.
  • “How do you protect the origin?” - firewall it to Cloudflare’s ranges, require Authenticated Origin Pulls, or use a Tunnel so it has no inbound ports.
  • “Can a Matrix homeserver sit behind Cloudflare?” - yes, with .well-known delegation so federation uses 443: port 8448 is not proxied. TURN stays DNS-only, and requests over 125 seconds get a 524.
  • “What does Cloudflare cache by default?” - static files by extension, not HTML or JSON, and nothing with Set-Cookie or private, no-store, no-cache or max-age=0.