misc · May 16, 2026 · 4 min read
My VPN wasn't enough.
Context
I was on a website, connected through ProtonVPN, when I noticed something strange. The phone number field was pre-filled with my real country code. Not the Netherlands, not whatever country my VPN exit node was in. My actual country.
That shouldn’t happen. I had WebRTC disabled, I was behind a VPN, and I was using Brave which handles most of the obvious leak vectors out of the box. So I started digging.
The obvious suspects
The usual culprits when your real location leaks through a VPN are:
- WebRTC: browsers expose local and public IPs through STUN requests, bypassing the VPN tunnel entirely
- DNS leaks: DNS queries go out through the default resolver instead of the VPN
- The IP itself: kill switch not active, VPN dropped silently
I checked all of these. WebRTC was already disabled in Brave settings. DNS was going through the VPN. The IP showing on whoer.net was the ProtonVPN exit node. Everything looked clean.
But the site still knew where I was.
Same setup, different browser
I decided to test the same URL with a different browser. Mullvad Browser is built specifically around privacy. It ships with WebRTC disabled, a standardized fingerprint, and a set of protections that most browsers leave to the user to configure manually.
Same VPN, same network, different browser. The phone field pre-filled with the Netherlands. The problem was gone.
So the leak wasn’t coming from the network layer. It was coming from the browser itself.
Going through it one by one
I went through browserleaks.com section by section.
WebRTC: no leak. Public IP matched the VPN exit node, no local IP exposed.
IP: clean, showing the ProtonVPN address.
Then I hit the JavaScript section and looked at the timeZone field.
timeZone: Africa/RedactedMy real timezone, sitting right there in plain text.
One line of JavaScript, no permission needed
The browser exposes the system timezone through the Intl API. Any site can read it with one line of JavaScript:
Intl.DateTimeFormat().resolvedOptions().timeZone// "Africa/Redacted"No permission required, no prompt, nothing for the user to notice.
The VPN changes your IP to a Dutch exit node, but your system clock still says West Africa Time. The site sees an IP from the Netherlands with a timezone from West Africa and draws the obvious conclusion.
Mullvad Browser normalizes this. It reports a timezone consistent with the browser’s fingerprint instead of exposing the real system value. Brave doesn’t do this by default.
The fix
The manual approach is to change the system timezone before launching the browser (I use timedatectl on Linux):
timedatectl set-timezone Europe/AmsterdamThat works but it’s easy to forget, and it changes the timezone system-wide which is annoying if you switch VPN nodes often.
A cleaner approach for me is a small wrapper script. Instead of launching the browser directly, the script first queries ipinfo.io to get the timezone of your current exit node, then launches the browser with TZ set to that value. The system timezone stays untouched.
Don’t forget to kill all browser instances before running the script, otherwise you might end up with a mix of tabs with different timezones.
#!/usr/bin/env bashBROWSER=zen-browser
ipinfo=$(curl -fsSL --max-time 5 https://ipinfo.io) || { printf 'error: failed to fetch ipinfo\n' >&2 exec "$BROWSER"}
ip=$(printf '%s' "$ipinfo" | jq -r '.ip')timezone=$(printf '%s' "$ipinfo" | jq -r '.timezone')
if [[ -z "$timezone" || "$timezone" == "null" ]]; then printf 'warn: could not resolve timezone, launching without TZ override\n' >&2 exec "$BROWSER"fi
exec env TZ="$timezone" "$BROWSER"Save it somewhere in your $PATH, make it executable, and use it instead of launching the browser directly. If ipinfo.io is unreachable or returns garbage, it falls back to launching the browser normally so nothing breaks.
The browser now sees a timezone consistent with the VPN exit node, so the Intl API returns a value that matches the IP.
Hardening isn’t a checklist
Hardening a browser against leaks isn’t just about WebRTC and DNS. The Intl API, the navigator object, canvas fingerprinting, font enumeration, all of these are readable by any page you visit without any special permissions.
Mullvad Browser addresses this by standardizing the values it exposes. Most browsers don’t.
If you’re serious about not leaking your real location, the timezone has to match the exit node. Otherwise you’re handing out a near-precise geolocation signal to every site you visit, VPN or not.