DEV Community

Cover image for Three things change when you put Cloudflare in front of your host
Eugen Taranowski
Eugen Taranowski

Posted on Originally published at watchnext.leyu.studio

Three things change when you put Cloudflare in front of your host

Originally published on the WatchNext blog.

WatchNext's DNS lives on Cloudflare with the proxy enabled, in front of Netlify. That arrangement is extremely common and almost entirely uneventful: requests get faster, the origin gets shielded, and nothing appears to change.

Nothing appears to. Three things did, and none of them failed loudly enough to notice on its own.

1. Your host's country header starts describing your datacenter

WatchNext exists to answer one question — when does the next episode of this show air, in your timezone — so it needs to know roughly where the visitor is. Around 200 countries, each with its own idea of what day it currently is.

That used to be a browser-side call to a third-party IP geolocation service. Which meant every visitor's IP address was handed to an undisclosed company on their first page load, before any consent, and the app inherited that service's rate limits and downtime. The request already arrives at our own infrastructure, and hosts attach the country they resolved at the edge, so the answer was available for free with no extra party involved.

Every platform spells it differently — Netlify sends x-nf-geo as base64-encoded JSON, Vercel sends x-vercel-ip-country, Cloudflare sends cf-ipcountry — so the sensible thing is to read whichever is present.

Here is the part that matters once a proxy is in front. Your origin no longer receives connections from visitors. It receives them from Cloudflare. So the header your host generates describes the machine that relayed the request — a datacenter — rather than the person who made it.

const country =
  h.get("cf-ipcountry") ??                 // Cloudflare (proxied)
  readNetlifyGeo(h.get("x-nf-geo")) ??     // Netlify
  h.get("x-vercel-ip-country") ??          // Vercel
  h.get("x-country") ??                    // other proxies / self-hosted
  null;
Enter fullscreen mode Exit fullscreen mode

Cloudflare first, deliberately. cf-ipcountry only exists when Cloudflare is actually proxying, and when it exists it is the only header in that list still describing the visitor. When Cloudflare isn't in front, it is simply absent and the host's own value is correct again.

The reason this is worth a section rather than a footnote is the failure mode. A geolocation lookup reading the wrong header does not throw, log anything, or return an obviously bogus value. It returns a real, well-formed country code — just the datacenter's. Our requests have come through Amsterdam and Munich on different days. For a largely European audience, "Netherlands" and "Germany" are answers plausible enough to survive a casual look at the page.

Two more Cloudflare values worth handling: XX when it can't determine a country, and T1 for traffic arriving over Tor. Both are two ASCII letters, so both sail through a naive /^[A-Za-z]{2}$/ check and become a "country" your lookups will never match.

2. Responses start coming from a layer you didn't configure

After the proxy goes on, every response says this:

server: cloudflare
Enter fullscreen mode Exit fullscreen mode

Which tells you almost nothing. It identifies who handed the response over, not who produced it. The useful signal is whether your host's fingerprint is still on it — on Netlify that is x-nf-request-id.

We found this while looking at something trivial: a www-to-apex redirect on the studio site next door. It reported server: cloudflare, and also carried an x-nf-request-id. Both at once, which is the tell — Netlify generated that redirect and Cloudflare merely relayed it. The request had travelled all the way to the origin to be told to go somewhere else.

Arriving over plain HTTP cost two of those round trips: one to upgrade to HTTPS, another to drop the www. Moving the rule into Cloudflare's own redirect rules, so it is answered at the edge, collapsed it:

Request Before After
http://www 2 hops, both from the origin 1 hop, from the edge
https://www 1 hop, from the origin 1 hop, from the edge

Afterwards the 301 still says server: cloudflare — but no longer carries x-nf-request-id, which is how you know the rule is really being served at the edge rather than quietly passing through.

The same question is worth asking about headers. WatchNext's security headers are declared by the application and emitted by its runtime, and you can see that from the outside: they arrive on responses that carry the host's request id. When a header you've configured somewhere isn't appearing, "which layer is supposed to be adding this, and did the response even come from there?" is usually a faster question than re-reading the config.

3. Your privacy policy has a processor it doesn't name

This is the one we got wrong for a while, and it is the reason this post exists.

Our privacy policy named our host. It did not name Cloudflare. But once the proxy is on, every single request reaches Cloudflare before it reaches the host — so Cloudflare processes every visitor's IP address, user agent and requested URL. And, per the section above, we actively depend on it doing so: the country we use to localise air dates is a value Cloudflare computed.

That is not a technicality about a CDN. In the arrangement we had, the party doing the most visible processing of visitor data was the one the policy didn't mention. So Cloudflare went into all four places that enumerate who touches the data: the usage-data description, the list of service providers, the international-transfers section, and the third-party policy links.

One addition mattered more than the rest. Cloudflare may set a strictly necessary security cookie when it sees suspicious traffic. Our cookie section claims to enumerate everything that gets stored on your device — so leaving that out didn't merely omit something, it made a statement we were publishing inaccurate.

Nothing here is legal advice, and the specific obligations differ by jurisdiction — check yours. The engineering point is the transferable one: adding a proxy adds an organisation that processes your visitors' personal data. That makes it a documentation change as much as an infrastructure change, and the documentation is the half that has no deploy step to remind you.

The pattern underneath all three

A proxy is not a transparent pipe. It terminates TLS, it can answer requests without consulting your origin at all, it changes what your origin is able to see about the client, and it handles personal data on the way through.

Every one of those is genuinely useful — that is what you turned it on for. But each has a consequence you have to go looking for, because the symptom of getting it wrong is never an error. It is a page that loads faster while quietly telling someone in Vietnam what airs tonight in Amsterdam.

Two questions catch most of it. Who actually answered this response? — check for your host's fingerprint rather than trusting the server header. And what did each layer see? — because anything that sees a visitor's IP address is a party you are relying on, whether or not you have written it down.

We had both answers available the whole time, in response headers anyone can read with curl -I. We just hadn't thought to ask until a redirect looked one hop too long.


WatchNext is a deliberately simple TV tracker: it tells you when the next episode of your favourite shows airs, for any show, in any country — and nothing else.

Top comments (0)