Timezone and locale consistency, the most common self-inflicted tell
Routing traffic through a proxy changes one thing: the IP address a server sees. It changes nothing about the operating system underneath the browser, which keeps reporting its own timezone, its own language, and its own locale formatting exactly as configured. A session claiming a residential IP in one country while its clock, language, and number formatting all belong to another is not a subtle signal. It is arguably the single cheapest check available to any site, and it needs no fingerprinting script at all to catch.
Every place the timezone leaks
Intl.DateTimeFormat().resolvedOptions().timeZonereturns the IANA zone name directly, such asAmerica/Los_AngelesorEurope/Berlin.Date.prototype.getTimezoneOffset()returns the offset from UTC in minutes for the current date, which must match what that IANA zone implies right now, including whichever side of a daylight saving transition the current date falls on.Date.prototype.toString()embeds both the numeric offset and a human-readable zone name, for example a string ending inGMT+0100 (Central European Standard Time). A script can read this without touchingIntlat all, so overriding only one of the three leaves the other two intact and disagreeing.
Every place the locale leaks
navigator.languageandnavigator.languagesreport the preferred language and its ordered fallback list.- The
Accept-LanguageHTTP header is generated by the browser from that same language list and sent with every request, including the very first one, before any JavaScript runs. A tool that changesnavigator.languagebut not the header disagrees with itself on the server's own request log. Intl.NumberFormatformats numbers using locale-specific separators, so1234567.89renders with a period and comma in an English locale but a comma and period swapped in much of continental Europe. A page can format a known number and compare the output string against the locale it was told.Intl.Collatorgoverns sort order for strings, and locales disagree on where certain accented or non-Latin characters sort relative to the plain alphabet. Sorting the same short list and hashing the order is a valid, if rarely used, consistency check.
Geolocation, if it is granted
navigator.geolocation.getCurrentPosition() requires an explicit permission prompt, so it is not always available, but when a user accepts it the coordinates it returns need to fall inside the same country the IP address geolocates to and the same timezone the clock reports. Three independent signals agreeing is a much stronger claim than any one of them alone, which is exactly why sites that get the permission tend to check it against the other two rather than trusting it in isolation.
The check that needs no JavaScript at all
A server can catch a mismatch without ever running a fingerprinting script, using only its own request log. It looks up the source IP's country and typical UTC offset in a geoIP database, then compares that against any timestamp the client already sent for an unrelated reason, such as a cookie-consent banner's recorded local time, an order form's local time field, or the client clock value in a websocket handshake. If the IP geolocates to a timezone eight hours off UTC but the client-supplied timestamp implies a clock five hours off UTC, that gap exists in plain log data, no script property required. This is why a proxy change alone, with the operating system left on its own time and language settings, is such a common and easy failure.
Checklist
Intl.DateTimeFormat().resolvedOptions().timeZonematches the exit IP's region.getTimezoneOffset()matches what that IANA zone implies for the current date.navigator.languageandnavigator.languagesmatch the expected locale.- The
Accept-Languageheader, not just the JS-visible property, matches too. Intl.NumberFormatandIntl.Collatoroutput looks native to the claimed locale.- Geolocation, if granted, agrees with both the IP and the clock.
Binding a proxy to a profile makes timezone, locale, and geolocation follow the exit IP automatically, which removes the step where this usually goes wrong: someone changes the proxy for a session and forgets that nothing else in the stack moved with it.
Where a free, simpler fix is the right answer
If you only need to preview a page once as if you were in another timezone, Chrome DevTools' Sensors panel already overrides geolocation for free, and your operating system's own locale and timezone settings, changed for the session, cover the rest of a one-off manual check. Neither needs a separate browser profile or any proxy at all. What that approach does not solve is keeping many persistent identities each internally consistent across restarts, weeks apart, without you remembering to re-check six properties by hand every time. That is a different, ongoing problem from previewing one page once, and it is worth being honest about which one you actually have before reaching for anything heavier.
Verify it yourself
function localeReport() {
return {
timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
offsetMinutes: new Date().getTimezoneOffset(),
dateString: new Date().toString(),
language: navigator.language,
languages: navigator.languages,
numberSample: new Intl.NumberFormat().format(1234567.89),
}
}
console.log(localeReport())
Compare the timeZone and offsetMinutes fields against where your exit IP actually is, for example with any IP-lookup site, and compare language against the Accept-Language header your browser sends, visible in your network devtools on any request. For the exit IP side of this problem, see residential versus datacenter proxies, and for the fuller list of properties that need to agree with each other beyond timezone and locale, the fingerprint consistency checklist.
Try it on the free tier.
Unlimited local profiles, no credit card. Check it against the detectors yourself.