๐ŸŽ‰ Premium Proxies ยท 24-Hour Free TrialClaim Now
Guides

Proxy Showing Wrong Country? Align Every Geo Signal

Proxy showing wrong country content even though the IP checks out? Test cookies, Accept-Language, timezone, CDN edge and geo-IP lag in order, then fix each one.

S SparkProxy 2 18 min read
Share

A proxy showing wrong country content is usually not a proxy fault at all. Your IP checker says Germany, the exit really is German, and the shop still greets you in English with prices in dollars. That happens because a website decides where you are from several inputs, and the IP is only one of them. This guide walks through each of the other inputs in the order that costs least to test, gives you a check for every one, and shows how to make all of them agree with the proxy country.

What You See When the Signals Disagree

The reports we get from customers almost always take one of these shapes:

  • "My IP checker says Frankfurt, but the store shows USD and a US delivery banner."
  • "I switched my proxy from the US to Japan and the site still shows US prices."
  • "Search results are in the right country but the language is English."
  • "Some requests come back German, some come back American, with the same exit country."
  • "The country is right in curl and wrong in my headless browser."

Each phrase points somewhere different. The second one is nearly always a cookie. The third is almost always a missing or wrong Accept-Language header. The fifth points at the browser's locale and timezone. The fourth is the interesting one, and it usually means some of your exits are mapped to a different country in the database the site licenses, or a CDN cache is serving one country's page to everyone.

Write down which shape you have before you start. It tells you which step below will most likely end the search.

Six Inputs a Site Uses to Pick Your Country

A proxy changes exactly one of these: the IP address the site sees. The rest come from your client, your history with the site, or the site's own infrastructure.

InputWhere it comes fromQuick checkCan the proxy fix it?
Geo-IP lookup of the exitThe site's or CDN's geo database`/cdn-cgi/trace` `loc=` on Cloudflare sitesYes, with a different exit
Stored cookie or saved regionA previous visit, a persistent browser profileRetry with an empty cookie jarNo. Clear state on your side
`Accept-Language` headerYour HTTP client or browser localeEcho your own request headersNo. Set the header
Browser locale and timezoneThe machine running the browserRead `navigator.language` and `Intl` timezoneNo. Set them in the browser context
CDN edge chosen by DNSWhoever resolved the hostnameCompare the connected IP across exitsPartly. Resolve at the proxy
Account, cache or app logicThe site itselfCompare logged out, cache headersNo

Two columns matter here. The quick checks are what the steps below expand on. The last column is the honest part: a different proxy can only repair the first row and part of the fifth. If the cause sits in any other row, buying exits in more countries won't change the page. For how the IP row works under the hood, what geo-targeting means in proxies covers the product side.

Free trial

Scraping at scale? Skip the blocks.

Fast, unblockable datacentre proxies with unlimited bandwidth.

Step 1: Ask the Site's Edge Which Country It Sees

Before touching headers, find out whether the site agrees with your IP checker. Many sites sit behind Cloudflare, and Cloudflare documents a troubleshooting endpoint at https:///cdn-cgi/trace (Cloudflare docs, "cdn-cgi endpoint", read 2026-10-30). Request it on the target's own hostname, through the proxy:

curl -s -x "$PROXY_URL" https://www.sparkproxy.io/cdn-cgi/trace | grep -E "^(ip|loc|colo)="

Run directly from our test machine on 2026-10-30, it returned:

ip=49.37.250.31
colo=CDG
loc=IN

Read loc, not colo. loc is the country Cloudflare's edge assigned to the connecting IP (our Indian address read IN). Cloudflare can hand its country lookup to the site's origin in the CF-IPCountry header, documented in its HTTP headers reference, so loc is a close stand-in for what the site's code receives. The trace fields aren't documented one by one, so treat loc as a strong hint rather than a contract. colo is just the data center that answered. Our Indian IP was served from Paris (CDG), and nobody's content should depend on that. Through a proxy, ip should show the exit address and loc its country.

This one request splits the problem in two:

  • loc is wrong. The site's edge really thinks the exit is elsewhere. Go to step 6 (database lag), and try a few other exits from the same country.
  • loc is right, the page is still wrong. The IP is fine. Something in your client or your history is overriding it, so work through steps 2 to 5.

Sites that aren't on Cloudflare don't expose this endpoint. On those, look for the site's own region marker in the HTML (a country selector, a currency code, a lang attribute) and use that as your reading instead.

Step 2: Rule Out Stored Cookies and Saved Preferences

This is the cheapest test and the most common cause. Many sites store your region the first time they see you, in a cookie or in localStorage, and then trust that stored choice over the IP on every later visit. If your first request went out through a US exit, the site remembered "US". Switching the proxy to Japan doesn't erase that memory.

The test is to repeat the request with no state at all:

import os

import requests

PROXY = os.environ["PROXY_URL"]  # your SparkProxy endpoint for the target country
URL = "https://shop.sparkproxy.io/product/123"


def fetch(session):
    r = session.get(URL, proxies={"http": PROXY, "https": PROXY}, timeout=30)
    return r.status_code, "EUR" in r.text, sorted(session.cookies.keys())


reused = requests.Session()
reused.cookies.set("region", "US", domain="shop.sparkproxy.io")  # simulate an old visit
print("with old cookie:", fetch(reused))

with requests.Session() as clean:
    print("empty cookie jar:", fetch(clean))

If the clean session gets the right country and the reused one doesn't, you've found it. Replace region and EUR with the cookie name and currency marker your target actually uses; the cookie list printed by the clean run shows you what the site sets on a first visit.

The same trap hides in browsers. A Playwright launch_persistent_context() keeps cookies and storage between runs, as does a Selenium profile directory. Fresh new_context() calls start empty. If your job rotates countries, give each country its own context and its own cookie jar. Where you need a stable identity inside one country (a login, a cart), use Sticky Sessions so the exit stays fixed for the session, and keep the cookie jar tied to that session. More on keeping session state tidy is in handling cookies and sessions in web scraping.

Step 3: Send an Accept-Language That Matches the Exit

When the country is right but the language is wrong, check what your client sends. We ran both common command-line clients against a local header echo server on 2026-10-30: Python Requests 2.32.5 sent User-Agent, Accept-Encoding, Accept and Connection, and no Accept-Language at all. curl 8.15.0 didn't send one either.

A site that gets no language preference has to guess. Many fall back to their default language, which on international sites is often English, so a perfectly German IP still gets the English page. A browser does send the header, but it builds it from the machine's locale. Headless Chromium on our en-US Windows server sent en-US,en;q=0.9 regardless of which proxy it used.

The fix is a header that matches the exit country:

import os

import requests

PROXY = os.environ["PROXY_URL"]
HEADERS = {
    "Accept-Language": "de-DE,de;q=0.9,en;q=0.5",
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
    "(KHTML, like Gecko) Chrome/151.0.0.0 Safari/537.36",
}

with requests.Session() as s:
    s.headers.update(HEADERS)
    r = s.get(
        "https://shop.sparkproxy.io/",
        proxies={"http": PROXY, "https": PROXY},
        timeout=30,
    )
    print(r.status_code, r.headers.get("Content-Language"), r.url)

Watch Content-Language and the final URL. Sites that localize by header often redirect to a path such as /de/ or answer with Content-Language: de. If both stay English with a German header and a German loc, the site probably decides language from an account setting or a cookie, which takes you back to step 2.

Keep the header plausible. A q= list with the local language first and English as a low-weight fallback is what multilingual visitors send; a lone fr on a request from a Brazilian exit is a mismatch some bot filters notice.

Step 4: Align Browser Locale and Timezone

Browsers expose two more location hints to the page's JavaScript: the language list in navigator.language and the timezone from Intl.DateTimeFormat().resolvedOptions().timeZone. Both come from the operating system running the browser, not from the network. Playwright's documentation says the context option locale "will affect navigator.language value, Accept-Language request header value as well as number and date formatting rules", and that timezone_id "changes the timezone of the context". Both default to the system settings.

We measured what that means in practice with Playwright for Python 1.63.0 driving Chromium 151.0.7922.34 on a server set to India Standard Time:

Context options`Accept-Language` sent`navigator.language`Timezone seen by the page
none`en-US,en;q=0.9``en-US``Asia/Calcutta`
`locale="de-DE"`, `timezone_id="Europe/Berlin"``de-DE``de-DE``Europe/Berlin`
`extra_http_headers` only, German list`de-DE,de;q=0.9,en;q=0.5``en-US`system default
`locale="de-DE"` plus `extra_http_headers``de-DE``de-DE`as set

The third row is the trap we didn't expect. Setting only the header fixes what the server sees, but the page's own scripts still read en-US, so a site that localizes in JavaScript (or a fraud script comparing the two) sees an English browser on a German IP. The fourth row shows that when both are set, the locale value won on the navigation request in our test. Set locale and timezone_id together and treat them as the source of truth.

A site can also ask for physical position through the Geolocation API, which reports the device's real location if permission is granted. That's a separate signal again, and it's covered in the limitations section of the geo-targeting guide. WebRTC can expose a non-proxy address in browsers too; see WebRTC leaks if your checks show a second IP.

These map well for the common targets. For countries spanning several zones, match the zone to the exit city:

Exit country`locale``timezone_id`
Germany`de-DE``Europe/Berlin`
France`fr-FR``Europe/Paris`
United Kingdom`en-GB``Europe/London`
Japan`ja-JP``Asia/Tokyo`
Brazil`pt-BR``America/Sao_Paulo`
United States (East)`en-US``America/New_York`
United States (West)`en-US``America/Los_Angeles`

Step 5: Check Which CDN Edge DNS Sent You To

Large CDNs answer DNS with an edge near whoever asked. If your machine resolves the hostname and then sends the resulting IP through a foreign proxy, you reach an edge picked for your location, not the exit's. The full mechanism, including EDNS Client Subnet and the centralised-resolver case, is in how proxies handle DNS resolution; here's the check.

Compare the address you actually connected to across two exit countries:

for P in "$PROXY_DE" "$PROXY_JP"; do
  curl -s -o /dev/null -x "$P" -w "%{remote_ip} %{http_code}\n" https://shop.sparkproxy.io/
done

Behind an HTTP proxy, remote_ip is the proxy itself, so this check is most useful with SOCKS. Run it with socks5:// and then socks5h:// URLs: with socks5:// your machine resolves the name, with socks5h:// the proxy does. If the page only goes wrong with socks5://, switch the scheme and you're done. If the edge is identical from a German and a Japanese exit even with socks5h://, resolution isn't following your exit, and that's a question for the provider's resolver setup. Edge choice usually affects latency more than content, so treat this as a cause only when the other steps are clean.

Step 6: When the Geo-IP Database Is Behind

If step 1 showed the wrong loc, the site's geo data disagrees with your IP checker. That isn't surprising: they're often different databases, refreshed on different schedules.

Two published cadences show where the lag comes from. MaxMind's support article on database updates (read 2026-10-30) lists GeoIP Country as updated every weekday and the free GeoLite Country every Tuesday and Friday. Cloudflare's IP geolocation docs (read 2026-10-30) say it updates its database "multiple times per week" and that confirmed corrections "generally take effect within a few business days", adding that it "cannot guarantee that its IP geolocation will align with other providers". Those are the vendor's side. The site's side adds its own delay: MaxMind's own example geoipupdate crontab runs twice a week, and a site that downloads a file once and never updates keeps an old picture for months.

So an IP range that moved countries recently can be correct in one checker and wrong at the site for days or longer. What you can do:

  1. Sample more exits. Request /cdn-cgi/trace through ten fresh connections from the same country and count the loc values. If only a few exits are off, drop them and move on.
  2. Report the range. Cloudflare and MaxMind both run correction forms; give the provider the exit IPs and the loc you saw so they can push a fix through their geofeed.
  3. Don't trust one checker. How to test across registration data, commercial databases and the target's own view is laid out in validating IP geolocation accuracy.

This is the one cause in the guide that's properly the proxy's to fix, and the fix is a different IP, not a different setting.

Causes No Proxy Can Fix

Some wrong-country pages are decided after the IP has been read correctly. No exit change helps with these:

  • A shared CDN cache. If the site caches pages without keying them by country, the first visitor's version gets served to everyone. Look at the response headers: CF-Cache-Status: HIT or a large Age value on a page that should vary by country, and no Vary header naming a country or language input, point at the cache. Adding a harmless query string such as ?nocache=1 often bypasses it; if that alone changes the country, you've confirmed it.
  • Account region. Logged-in sessions usually follow the account's billing or shipping country. Test logged out.
  • Payment and licensing rules. Store catalogs and streaming libraries can be tied to the payment method's country, not the visitor's location.
  • App logic that pins a region. Some sites redirect to a regional subdomain once and then stop checking.

A Playwright Client With Every Signal Aligned

Putting steps 1 to 4 together: one context per country, an empty cookie jar, matching locale and timezone, and a loc check before the real page.

import os

from playwright.sync_api import sync_playwright

PROFILES = {
    "DE": {"locale": "de-DE", "timezone_id": "Europe/Berlin"},
    "JP": {"locale": "ja-JP", "timezone_id": "Asia/Tokyo"},
}


def edge_country(page, host):
    page.goto("https://" + host + "/cdn-cgi/trace")
    text = page.inner_text("body")
    fields = dict(line.split("=", 1) for line in text.splitlines() if "=" in line)
    return fields.get("loc"), fields.get("ip")


def scrape(country, url, host):
    proxy = {
        "server": os.environ["PROXY_SERVER_" + country],  # e.g. http://gateway.sparkproxy.io:11000
        "username": os.environ["PROXY_USER_" + country],
        "password": os.environ["PROXY_PASS"],
    }
    with sync_playwright() as p:
        browser = p.chromium.launch(proxy=proxy)
        context = browser.new_context(**PROFILES[country])  # fresh cookies every run
        page = context.new_page()
        loc, ip = edge_country(page, host)
        if loc != country:
            browser.close()
            raise RuntimeError("exit {} geolocates to {}, not {}".format(ip, loc, country))
        page.goto(url, wait_until="domcontentloaded")
        signals = page.evaluate(
            "() => [navigator.language, Intl.DateTimeFormat().resolvedOptions().timeZone]"
        )
        html = page.content()
        browser.close()
        return signals, html


signals, html = scrape("DE", "https://shop.sparkproxy.io/", "shop.sparkproxy.io")
print(signals, len(html))

The edge_country check only works on Cloudflare-fronted hosts; for others, replace it with a marker from the site's own HTML. Raising on a mismatch is deliberate. Retrying through a fresh connection gets another exit under Random Rotation, which is the cheap fix for step 6, and SparkProxy plans include unlimited bandwidth (plans start from $75/month in USD, per sparkproxy.io in October 2026), so a discarded check costs concurrency time rather than traffic.

Country-Correct Fetches With the Scraping API

If you'd rather not manage browsers, the SparkProxy Scraping API takes the country and the language header as parameters. Per the Scraping API docs read 2026-10-30: base https://scrape.sparkproxy.io/api/v1, header X-API-Key, country_code takes an ISO 3166-1 alpha-2 code and adds 5 credits, and forward_headers is a JSON object merged with the browser defaults, where any key you set overrides the default. render_js defaults to true (5 credits); render_js=false is a plain fetch at 1 credit. The docs also say session_id "does not persist cookies across requests", so each call starts without stored region cookies, which removes step 2 entirely.

import base64
import json
import os

import requests

API = "https://scrape.sparkproxy.io/api/v1"
HEADERS = {"X-API-Key": os.environ["SPARKPROXY_API_KEY"]}


def fetch_as(country, lang, url, render=False):
    params = {
        "url": url,
        "country_code": country,
        "forward_headers": json.dumps({"Accept-Language": lang}),
        "render_js": "true" if render else "false",
        "json_response": "true",
    }
    r = requests.get(API, params=params, headers=HEADERS, timeout=120)
    r.raise_for_status()
    env = r.json()
    body = base64.b64decode(env.get("body") or "").decode("utf-8", "replace")
    return env.get("status_code"), env.get("credits_used"), body


status, credits, body = fetch_as("DE", "de-DE,de;q=0.9,en;q=0.5", "https://shop.sparkproxy.io/")
print(status, credits, "EUR" in body)

json_response=true is what returns the envelope (job_id, status_code, duration_ms, credits_used, body), and body is base64, so the code decodes it before looking for the currency marker. Don't confuse that with format=json, which converts page content and doesn't produce the envelope. The docs list no timezone or locale parameter today, so when a target localizes in JavaScript, render with render_js=true and check the result rather than assuming the browser's timezone matches the country. We couldn't capture a live response for this guide (the API edge returned a Cloudflare 521 on 2026-10-30), so the envelope fields above come from the documentation.

Frequently asked questions

FAQ

Because the site also reads stored cookies, your Accept-Language header, the browser's locale and timezone, and its own geo database. Any one of those can override a correct IP, and a stored region cookie from an earlier visit is the most common culprit.

On Cloudflare-fronted sites, request https:///cdn-cgi/trace through the proxy and read the loc= line, which is the country Cloudflare assigned to your exit. On other sites, use the site's own currency, language or region selector as the reading.

Often for language, sometimes for country. Python Requests and curl send no Accept-Language by default, so many sites fall back to English; set a header that matches the exit, such as de-DE,de;q=0.9,en;q=0.5 for Germany.

Pass locale and timezone_id to new_context(), for example locale="de-DE" and timezone_id="Europe/Berlin". In our test, setting only an Accept-Language header left navigator.language at the system value, so set both options rather than the header alone.

Yes. MaxMind publishes its country data on a weekday or twice-weekly schedule and Cloudflare says corrections take a few business days, and sites that rarely download updates lag further. Try other exits from the same country and report the bad range.

Only when the site's geo lookup is wrong about the exit. If the edge reports the right country, the cause is cookies, headers, locale, caching or your account region, and a new IP won't change the page.

Special Discount ยท 20% off

Get 20% off your first month

Premium datacentre proxies with unlimited bandwidth. Use the code at checkout.

Save up to 15% more on quarterly, half-yearly and yearly plans

Claim Discount

About the Author

The SparkProxy Technical Team runs SparkProxy's residential and datacenter proxy gateway and the Scraping API, and answers the geo-mismatch tickets this guide is built from. The header and browser measurements here were taken by the team on 2026-10-30 against a local echo server, and every vendor figure links back to that vendor's own documentation.

Keep reading

Related articles