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 uses a visitor's country for a small set of defaults: which streaming services to show beside a title, which age rating to display, and — if you ask for it — which shows were made where you live. None of that touches air dates, which always follow the show's own country. But get the visitor's country wrong and all three quietly point somewhere else.
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;
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:
XXwhen it can't determine a country, andT1for 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
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 behind those streaming services, ratings and local shows 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.
Correction, 14 September 2026: an earlier version of this post said WatchNext uses the visitor's country to localise air dates. It doesn't. Dates always follow the show's own country; the visitor's country only sets default streaming services, age ratings and the local-shows filter. A reader's question about VPNs prompted the recheck.
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 (4)
T1 is the narrow case. Someone on a commercial VPN gets a normal two-letter code that passes any validity check you write. Same silent failure, one layer out. Cheapest filter is the ASN behind the IP: M247, Datacamp, Mullvad ranges cover a lot of it. Residential proxies still slip through, but it beats trusting the code.
Fair — T1 was the tidy example, and the VPN is the general case: a well-formed code that passes every check and still doesn't describe the person.
Checking what that actually costs here turned up an error in the post itself, so first a correction. It said WatchNext uses the visitor's country to localise air dates. It doesn't, and never has — dates always follow the show's own country. The visitor's country only chooses three defaults: which streaming services to show, which age rating, and the optional filter for shows made in your country. It's corrected in the post, with a note.
That changes the answer to your point. For a VPN user, the exit country is often the right default here: most streaming services geolocate by IP too, so the catalogue they'll actually get is the VPN country's — and that's the one we'd show. A misdetection costs a wrong default, not a wrong date, and the visitor can override it in the Location panel; once set, that choice wins over detection from then on.
ASN filtering would also cut against how the lookup is built. It never sees the address: the route reads the country code the edge already computed, plus the browser's language, and nothing else — which is what the privacy policy promises. Checking M247 or Mullvad ranges means reading the IP and matching it against an ASN database. That's worth it for licensing, fraud or regional pricing, where a wrong country costs money. For a default the visitor can change themselves, I'd rather not start handling IPs to make it slightly more accurate.
Where your point really bites is anything that enforces on the country rather than defaulting from it. We don't have one of those, so the code stays trusted — deliberately, and now with the stakes written down.
Agreed. Enforcement is where we require two independent sources before flagging a datacenter ASN.
Some comments may only be visible to logged-in visitors. Sign in to view all comments.