Route your traffic through Amsterdam and Google greets you in Dutch. Nearly everyone who uses a VPN has hit this annoyance and shrugged it off as VPN weirdness. It isn’t weirdness. It is the leak this article is about, running in reverse.
The request contradicted itself. The packets came from a Dutch IP address; the headers said which languages you actually read: Accept-Language: en-US,en;q=0.9. For picking an interface language, Google trusts the IP. A fraud script on a checkout page runs the identical comparison the other way around: it checks whether the languages fit the IP, and files the answer into your risk score. The widget on this page runs that exact check against your browser, right now.
Accept-Language: a header on every request
When your browser asks a server for anything, a page, an image, a font, it attaches the Accept-Language HTTP header. The header exists so servers can pick a language you understand, and it is built from the language list in your browser settings, which your OS locale seeded when the browser was installed. The q=0.9 notation ranks the list: this browser prefers American English, and plain English is a close second. The full grammar is on MDN’s Accept-Language page.
That header is in the very first request you send. Not after some script loads, not after a cookie is set. The server reads it in the same breath as the source IP address on the packet:
198.51.100.7 geo database Amsterdam, NLGET /checkout HTTP/1.1 Host: shop.example User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) ... Accept-Language: en-US,en;q=0.9 â—„ the browser's own language list
JavaScript gets the same information through navigator.language and the full list through navigator.languages, no permission prompt involved. Note the difference with the clock: a script has to run and ask for your timezone; your language arrives before the page has any scripts to run.
Why a VPN doesn’t change your browser language
A VPN does exactly nothing about this. The tunnel moves your packets; it does not translate them. Whatever your browser wrote is delivered as written, including the header that says “my user reads English, not Dutch.” And rewriting it is not even on the table: on any HTTPS site, the header travels inside the TLS session between your browser and the site, which the VPN cannot open.
What a mismatch proves, and what it doesn’t
Language is a soft signal, softer than the timezone. English is the closest thing an operating system has to a factory setting, so en-US from an Amsterdam IP raises no eyebrows on its own. Belgium runs on three official languages, Switzerland on four. Expats and remote workers legitimately read a different language than their neighbors. Every serious detection system knows all of this, which is why a language mismatch nudges a score and nothing more.
But the signal has a sharp direction. A browser that prefers pt-BR behind a Dutch exit IP is not ambiguous the way English is: it says the person at the keyboard reads Brazilian Portuguese, and the Netherlands has no shortage of IP addresses but very few Portuguese-first households. Stack that against a datacenter IP and a clock still set to America/Sao_Paulo, and the packets claiming Amsterdam have lost the argument:
nl-NL, nl+ Amsterdam IPagrees The pair tells one story. Nothing to score.en-US, en+ Amsterdam IPambiguous English browsers are everywhere. A weak signal alone, but it counts when others pile on.pt-BR, pt+ Amsterdam IPmismatch The browser points at Brazil while the packets claim the Netherlands. Now you are interesting.- header:
nl-NL+ JavaScript:pt-BRspoof detected The browser disagrees with itself, and almost nothing except deliberate tampering does that.
There is a second, quieter cost. The exact composition of your list, which languages, in which order, with which q values, is fingerprint material. The EFF’s Panopticlick study measured the Accept headers at around six bits of identifying information, which is what makes Accept-Language fingerprinting work: en-US,en;q=0.9 hides in the crowd; nl-BE,fr-BE;q=0.9,en;q=0.8,de;q=0.7 is practically a signature. For comparison, your font list gives away more than twice as many bits — but a script has to measure that, while this header volunteers itself on every request.
Browser vendors know it. Safari sends only your single top language for exactly this reason, Chrome’s engineers have a proposal to trim the header to one language, and Brave deliberately blurs its answer per site. None of that helps with the mismatch, though: the one language that survives the trimming is still yours.
Why Google is in the wrong language with your VPN on
Back to the Dutch Google. If browsers politely announce their language in every request, why do sites ignore it the moment you connect through a VPN? Because they trust IP geolocation more, and they have since long before you had a VPN. Google said it out loud in a 2017 announcement, when it stopped keying its country versions to domains like google.nl and started serving whatever the detected location implies. The header states a preference; the IP is treated as a fact. The same hierarchy that annoys you on Google’s homepage is the one a fraud system applies when your header and your IP disagree: the IP wins, and your header is demoted from preference to evidence.
If the annoyance is your actual problem, the fix lives in your account settings, not your VPN settings: signed-in language preferences usually override IP guessing on Google and most large sites. The leak, however, has no settings-page fix, because the leak is the header itself.
The half-spoof trap, again
Readers of the timezone article have seen this trap. It is the same shape here and easier to fall into: the language exists in two places, the Accept-Language header on the network and navigator.languages in the JavaScript engine. In a normal browser they are two views of the same setting and always agree. Tooling that fakes one tends to forget the other.
Spoofing extensions mostly override the JavaScript side, because that is what an extension can easily reach; your header keeps telling every server the truth. Header-rewriting proxies do the reverse and leave navigator.languages reporting your real list to every script that asks. Either way the browser now disagrees with itself, and that contradiction is worth more to a detection system than the original mismatch, because ordinary VPN users produce mismatches all day while only deliberate tampering produces contradictions. On top of that, an overridden navigator.languages getter stops reporting [native code] when a script reads its source, the giveaway our bot detection article spends a section on.
The one spoof that works at scale is the boring one: in its default English configuration, Tor Browser answers en-US,en;q=0.5 for everyone, which hides your language by making you identical to every other Tor user. That trade, protection in exchange for obviously being Tor, is the honest version of what the halfway extensions pretend to offer.
What to actually do
- Run the check. The widget on this page compares your browser’s languages against your IP country right now, using the same country-to-language expectations a fraud script would. Do it with the VPN on, because that is the configuration your risk score is built from, then follow it with the full browser scan for the checks that live next door: timezone, WebRTC, fingerprints.
- Move your languages with your VPN. The clean fix mirrors the timezone advice: make the claim true. Add the exit country’s language to your browser’s list, in Chrome under
chrome://settings/languages, in Firefox under “Preferred languages”, and keep your real one alongside if you want; the check most systems run is “does any language fit,” not “do they all.” Just set it in the browser’s own settings instead of an extension, so both the header and the JavaScript change together. - If you run multiple identities, this is not a settings-page job. Every profile needs its languages, timezone and IP telling one story, every session, and hand-editing settings per identity does not survive contact with real work. Incogniton sets each profile’s language to fit that profile’s proxy, on the header and in JavaScript at once, so the browser never argues with its own packets.
Your browser told the last server you visited what language you read. The widget above tells you whether your IP backs that statement up.