Your app shows prices in euros to visitors from Germany, hides a feature in one country, and shows a cookie banner only in the EU. You tested it by overriding the location in Chrome DevTools. It passed. In production, a user in Berlin still sees dollars.
I've hit this pattern more than once, and the cause is always the same: the DevTools override changes one signal of location, and the server reads a different one. Below is the checklist I use now.
Disclosure: I work on Atomic VPN. The method works with any VPN or proxy that has exit servers in the countries you need, and I say where a VPN is the wrong tool at the end.
The four signals that mean "where is this user"
| Signal | Who reads it | How I fake it |
|---|---|---|
Geolocation API (navigator.geolocation) |
Frontend JS, after permission | DevTools Sensors, Playwright geolocation
|
| Client IP | Server, CDN, WAF | VPN, proxy |
Accept-Language, Intl locale, timezone |
Server and frontend | Playwright locale, timezoneId
|
| DNS resolver location | CDNs that route by resolver | A VPN with its own DNS, or change the resolver |
Most "works on my machine" geo bugs come from testing one row and shipping logic that depends on another.
What the DevTools override actually does
The Sensors panel lets you pick a preset city, enter coordinates, or simulate "location unavailable". It overrides what the page gets from the Geolocation API. That's right for a store locator or a weather widget.
It does not change the IP address of your requests. If your backend calls a GeoIP database, reads the CF-IPCountry header that Cloudflare adds, or your CDN applies country rules, all of them still see your real IP.
Step 1: check what the internet thinks my location is
Before any test, I check the exit IP and country. Cloudflare exposes a plain-text endpoint:
curl -s https://www.cloudflare.com/cdn-cgi/trace | grep -E '^(ip|loc|colo)='
You get three lines: your IP, a two-letter country code (loc), and the Cloudflare data center that served you (colo). Run it with the VPN off, then on. If loc doesn't change, the VPN isn't routing that traffic. That happens with split tunneling or a proxy set only in the browser.
Step 2: route traffic through the target country
I connect to a server in the country I want to test. When the app has region-specific rules, such as state-level restrictions in the US, I pick a location closest to that region. Then I run the curl check again and confirm loc.
Two things I verify at this point:
- The country matches in more than one database. IP geolocation is an estimate. Cloudflare's docs say nothing inherently binds an IP address to a country, and providers can disagree. If your app uses MaxMind and your CDN uses its own data, check both.
- DNS follows the tunnel. If the VPN doesn't handle DNS, your resolver may still sit in your home country, and a CDN that routes by resolver will send you to the wrong edge.
Step 3: add the other signals with Playwright
An IP in Germany with a browser set to US English and a New York timezone is not a German user. It's a user your own analytics will flag as odd. Playwright sets the rest in code:
import { chromium } from 'playwright';
const browser = await chromium.launch();
const context = await browser.newContext({
locale: 'de-DE',
timezoneId: 'Europe/Berlin',
geolocation: { latitude: 52.52, longitude: 13.405 },
permissions: ['geolocation'],
});
const page = await context.newPage();
await page.goto('https://your-app.example/pricing');
console.log(await page.locator('[data-test=price]').innerText());
await browser.close();
I run this while the VPN is connected to Germany. The IP, locale, timezone and coordinates now describe the same person.
I keep one script per target market and the expected values next to it. A short matrix is enough:
| Market | Expected currency | Consent banner | Blocked features |
|---|---|---|---|
| DE | EUR | yes | none |
| US-CA | USD | yes (state privacy notice) | none |
| JP | JPY | no | feature X |
What breaks, and why
- The VPN IP is classed as a datacenter or VPN address. Some services detect that and show a CAPTCHA, a paywall variant or a block page. That says something real about how those users see your app, but it isn't the same as a residential visitor.
- Shared IPs trigger rate limits. Many VPN users share an exit IP. A login form with aggressive IP throttling can lock you out mid-test.
-
Caches hide the bug. A CDN may serve you a cached "German" page, or serve your cached page to the next tester. Add a cache-busting query string, or send
Cache-Control: no-cache. -
Cookies carry state. A
country=UScookie from the first run beats the IP in the second. Playwright starts every context clean, which is why I use it for this. -
IPv6 leaks the real location. If the VPN tunnels only IPv4 and your network has IPv6, the app may see your real IPv6 address. Run
curl -6 https://www.cloudflare.com/cdn-cgi/traceand see whether it answers.
How I run this with Atomic VPN
Here's the workflow:
- Open the Atomic VPN homepage. It shows your current IP, city, ISP and DNS, so I get a "before" reading without leaving the browser.
- Connect in the app and pick the country (the app lists locations by country and city, with ping next to each).
- Reload the page and compare the "after" reading, then run the
curltrace from Step 1 to confirmloc. - Run the Playwright script for that market.
Why this works for me: there are 6,500+ servers in 88+ countries, so a new market is a click rather than a new proxy contract. The WireGuard protocol is the default, so connecting and switching countries is quick. If you're on a restrictive network that blocks UDP, there's an OpenVPN option over TCP 443.
Any VPN with similar coverage does the same job. If you need exit nodes in only one or two countries, a cheap VPS you control may be simpler (I wrote about that trade-off in Self-Hosted WireGuard vs a VPN Service). <!-- TODO (author): replace https://dev.to/ with the real URL of the second article after publishing it. -->
Where a VPN is the wrong tool
- CI. Pipelines need deterministic, scriptable exits. Use a proxy service with an API or geo-distributed runners, not a desktop VPN client.
- Compliance-critical rules. Cloudflare's docs advise against using IP geolocation as the sole signal when location must be precise, such as state-based access restrictions. I test that logic with a VPN, but I design it with more than the IP.
- Other people's services. Test your own app. Using a VPN to get around a third party's regional restrictions may break their terms, and in some places the law.
FAQ
Does Chrome DevTools location override change my IP?
No. It changes what the Geolocation API returns to the page. Server-side checks based on your IP see your real address.
Why does my app still show the wrong currency after I connect a VPN?
Usually a cookie, a cached response, or a second signal (locale, account country) that outranks the IP. Clear storage, use a fresh browser context, and check which signal your pricing logic reads first.
Can I test geo-blocking in CI with a VPN?
Not reliably. Use a proxy service with an API, or runners in the target regions.
Is IP geolocation accurate?
At country level, mostly. At city level it varies by provider and drifts as IP ranges change hands. Cloudflare states it can't guarantee its data matches other providers.
Why do some sites block my VPN exit IP?
Many services keep lists of datacenter and VPN ranges. The IP is shared and not residential, so it gets caught.
Is it legal to use a VPN to test my own app?
Using a VPN is legal in most countries. Check local rules, and don't use it to bypass restrictions on services you don't own.
Top comments (0)