Skip to content
ProxyForge

Geo-targeting proxies: how to verify country and city

ProxyForge engineeringUpdated 9 min read

Geo-targeting proxies let you choose the country, and with some providers the region or city, that your requests appear to come from. Selecting a country does not guarantee that a target sees that country. The target uses its own IP geolocation database, which may disagree with your provider's, along with signals such as Accept-Language, time zone and cookies. So verify: find the exit IP, look it up in at least two independent sources, check what the target itself reports, and keep your client's other signals consistent with the exit country.

This guide covers how geo-targeting works, why geolocation data disagrees, a Python script that flags mismatches, and the non-IP signals that most often give a location away.

How geo-targeting proxies work

There are two models, and they verify differently.

Static addresses are placed in a country when you buy them. ISP and datacenter proxies are individual addresses leased to you for a period. The address is routed from a specific network in a specific place, and its location does not change while you hold it. You verify it once on delivery, then periodically, because the databases that describe it can change even when the address does not.

Pooled addresses are selected per connection. Residential and mobile networks hold large pools of peer addresses spread across many countries. Where a provider offers targeting on a pool, you express it in the connection string, usually through parameters in the username or a dedicated endpoint, and the gateway picks an exit that its own data places in that location. The provider's dashboard generates that string; the exact syntax differs between vendors, so use what yours issues rather than copying a format from another provider's documentation.

The second model has a subtlety that trips up many verification scripts: on a rotating endpoint, every new connection can get a different exit. If you check the country with one request and then run your job, the job may not use the address you checked. Either verify inside the same sticky session the job uses, or verify a statistically useful sample of exits. Rotating vs sticky proxies explains how sessions pin an exit.

Why IP geolocation databases disagree

There is no authoritative registry of where an IP address is physically used. Every geolocation service builds an estimate from several imperfect sources, and weights them differently:

  • Registry data. Regional internet registries record the organization an address block is assigned to, and its country. That is where the holder is registered, not necessarily where the addresses are used. A multinational network may register everything in one country and use it in twenty.
  • Self-published feeds. Network operators can publish a geolocation feed in the format defined by RFC 8805, mapping their prefixes to countries and cities. Services that ingest feeds get accurate data for operators who publish them; many operators do not.
  • Measurements. Latency and traceroute measurements from known locations constrain where an address can plausibly be. They are good at ruling out continents and weak at choosing between nearby cities.
  • Observed usage. Some providers infer location from devices that report GPS or Wi-Fi positions alongside their IP address. This helps for consumer ranges and is uneven elsewhere.

On top of the method differences, there is timing. Address blocks are transferred between organizations and countries, networks renumber, and databases refresh on their own schedules. A service using a copy of a database that is a few weeks old will disagree with one updated yesterday. Mobile addresses add another layer: carrier-grade NAT gateways can serve a large region, so a phone in one city may appear from an address that geolocates to another city, or to the carrier's head office.

The practical consequence: two reputable services returning different countries for the same address is not rare, especially for recently transferred blocks. At the country level, most lookups agree most of the time. At the city level, disagreement is normal.

How to verify the exit country

A procedure that works for geo-targeting proxies from any provider:

  1. Get the exit IP through the proxy. Request a plain echo endpoint that returns the caller's address, through the same proxy configuration and session your job uses. This is the only step that needs to go through the proxy.
  2. Look up that IP in at least two independent sources, directly. Query the geolocation services for the specific address from your own network. Looking up by address avoids paying proxy bandwidth for lookups and keeps a rotating endpoint from giving each lookup a different exit.
  3. Compare the answers. All sources agreeing on the expected country is a pass. Sources disagreeing with each other is ambiguous: the address is in a contested block, and targets will split the same way. All sources returning another country is a mismatch.
  4. Check what the target thinks. Many sites reveal their verdict: a currency on the page, a redirect to a country storefront, a consent banner for a particular jurisdiction, or a location API the site calls itself. The target's verdict is the one that matters for your data.
  5. Sample, and record. For pooled addresses, check a sample of exits rather than one, and record the address, each source's answer, the verdict and the time. A mismatch rate over a sample is a number you can track and raise with a vendor; a single failed check is an anecdote.
  6. Repeat on a schedule. Databases change. An ISP address that was correctly placed on delivery can be relabeled months later.

A Python script that flags country mismatches

The script below implements steps 1 to 3 and 5. It reads the proxy URL from PROXY_URL, in the shape http://USERNAME:[email protected]:PORT, fetches the exit IP through it, then looks the address up in two public services directly, with trust_env disabled so that the lookups do not pick up a proxy from the environment. It exits non-zero if any sample is a mismatch or a disagreement, so it can run as a scheduled check.

import ipaddress
import os
import sys
from collections import Counter

import requests

PROXY_URL = os.environ["PROXY_URL"]
PROXIES = {"http": PROXY_URL, "https": PROXY_URL}
IPINFO_TOKEN = os.environ.get("IPINFO_TOKEN")

LOOKUPS = {
    "ipinfo": ("https://ipinfo.io/{ip}/json", "country"),
    "country.is": ("https://api.country.is/{ip}", "country"),
}


def exit_ip(session):
    response = session.get("https://api.ipify.org", proxies=PROXIES, timeout=30)
    response.raise_for_status()
    return str(ipaddress.ip_address(response.text.strip()))


def lookup_countries(session, ip):
    countries = {}
    for name, (template, field) in LOOKUPS.items():
        headers = {}
        if name == "ipinfo" and IPINFO_TOKEN:
            headers["Authorization"] = f"Bearer {IPINFO_TOKEN}"
        try:
            response = session.get(template.format(ip=ip), headers=headers, timeout=15)
            response.raise_for_status()
            value = response.json().get(field)
        except (requests.RequestException, ValueError):
            value = None
        countries[name] = value.upper() if value else None
    return countries


def verdict(expected, countries):
    answers = {c for c in countries.values() if c}
    if not answers:
        return "UNKNOWN"
    if answers == {expected}:
        return "OK" if None not in countries.values() else "OK_PARTIAL"
    if expected in answers:
        return "DISAGREE"
    return "MISMATCH"


def main(expected, samples):
    expected = expected.upper()
    tally = Counter()
    with requests.Session() as session:
        session.trust_env = False
        for _ in range(samples):
            try:
                ip = exit_ip(session)
            except requests.RequestException as exc:
                print(f"exit-ip check failed: {exc}")
                tally["ERROR"] += 1
                continue
            countries = lookup_countries(session, ip)
            result = verdict(expected, countries)
            tally[result] += 1
            print(f"{ip:<40} {result:<10} {countries}")

    print(dict(tally))
    return 1 if tally["MISMATCH"] + tally["DISAGREE"] else 0


if __name__ == "__main__":
    sys.exit(main(sys.argv[1], int(sys.argv[2]) if len(sys.argv) > 2 else 5))

Run it with the ISO country code you expect and the number of samples:

PROXY_URL="http://USERNAME:[email protected]:PORT" python geo_check.py DE 20

A few notes. The two lookup services are examples; any service that returns a country for a given address fits in LOOKUPS, and it is worth adding a third when you are evaluating a vendor. Free tiers are rate limited and have their own terms, so for scheduled checks at volume, use an API key or a licensed database you query locally. OK_PARTIAL means one source failed to answer and the rest agreed. With a sticky session, every sample returns the same address, which is what you want when checking the exit a job will actually use; with rotation, each sample is a different exit and the tally is a pool-level mismatch rate.

What targets use besides the IP address

The IP is usually the first signal a site reads and rarely the only one. The common others:

Signal Where it comes from How to keep it consistent
Accept-Language header Browser or HTTP client settings Send the exit country's main language, for example de-DE,de;q=0.9 for a German exit
navigator.language Browser locale Set the browser context locale to match the header
Time zone Operating system, read through JavaScript Intl and Date Set the context time zone to one in the exit country
Number, date and currency formats Derived from locale Follows from the locale setting
Cookies and local storage Earlier visits, including from another country Use a fresh context per country; never share a cookie jar across countries
Account, billing or shipping country Logged-in profile Log in only with accounts whose country matches, where you use accounts at all
GPS and Wi-Fi location Mobile apps and sites that request the Geolocation API Grant or deny permission deliberately; if granted, supply coordinates in the exit country
DNS resolution point Where the hostname was resolved Resolve at the proxy, as covered in SOCKS5 vs HTTP proxy

Mobile apps go further: they can read the SIM's country and carrier, the device locale and precise location. If an app workload requires a particular country, an exit address in that country is necessary but not sufficient.

Keeping the client consistent with the exit country

In a browser, set locale and time zone on the same context that carries the proxy. Playwright applies locale to both navigator.language and the Accept-Language header, and timezone_id to everything JavaScript reads about time:

import os
from urllib.parse import urlsplit

from playwright.sync_api import sync_playwright

proxy_url = urlsplit(os.environ["PROXY_URL"])
proxy = {
    "server": f"{proxy_url.scheme}://{proxy_url.hostname}:{proxy_url.port}",
    "username": proxy_url.username or "",
    "password": proxy_url.password or "",
}

with sync_playwright() as p:
    browser = p.chromium.launch()
    context = browser.new_context(
        proxy=proxy,
        locale="de-DE",
        timezone_id="Europe/Berlin",
        geolocation={"latitude": 52.52, "longitude": 13.405},
        permissions=["geolocation"],
    )
    page = context.new_page()
    page.goto("https://example.com")
    browser.close()

Grant geolocation only if the pages you work with ask for it and your use case calls for a location; otherwise leave the permission out. For HTTP clients without a browser, the equivalent is a consistent Accept-Language header on the session. The Playwright proxy guide covers the per-context proxy setup in more detail.

Reference values for some common exit countries:

Exit country Locale Example time zone Currency
United States en-US America/New_York (several zones) USD
United Kingdom en-GB Europe/London GBP
Germany de-DE Europe/Berlin EUR
France fr-FR Europe/Paris EUR
Japan ja-JP Asia/Tokyo JPY
Brazil pt-BR America/Sao_Paulo (several zones) BRL

For countries with several time zones, pick the one that matches where your lookups place the exit, or the most populous zone if you only know the country. Consistency reduces the number of contradictions a site can find; it does not make automated traffic invisible, and it is not a way around a site's terms. Use it so that the page you measure is the page a local visitor would see.

City-level targeting: accuracy caveats

City targeting multiplies every problem above, and it is where geo-targeting proxies most often fall short of expectations. The pool of exits a provider places in a given city is much smaller than its pool for the country, and the provider's placement comes from a geolocation estimate that may not match the target's estimate. Several consequences follow:

  • Expect disagreement between sources. Two services placing an address in neighboring cities, or one in a city and another only at the country level, is common. Compare at the region or metro level before you conclude anything.
  • Mobile addresses are the least precise. Carrier gateways can cover a large region, so a mobile exit targeted at one city may geolocate to another.
  • Check the target's own behavior. For local-results work, a store locator's default store, a delivery estimate or the local pack in search results tells you what the target decided, which is the only placement that affects your data.
  • Measure before you depend on it. Run the verification script at city level (most lookup services also return a city) against a sample of exits and record the agreement rate before building a workflow that assumes city precision.

If your work depends on precise location, such as local search rank tracking, test city-level results against a known reference first. See SEO and SERP tracking for how teams structure that work.

How ProxyForge handles location

On ProxyForge, ISP and datacenter addresses are ordered by country at checkout and stay in that country for as long as you hold them. Datacenter ranges are registered to us and announced from our own autonomous system, so their registry record is checkable independently; what is IP provenance explains how. Residential and mobile are sold as network-wide coverage; see the locations overview and each line's product page for what is orderable where, and the dashboard generates the connection string for whatever you select.

Whichever line you use, run the checks above against your own targets. If your verification turns up addresses that geolocate somewhere other than you ordered, a named engineer will look at the specific addresses with you, and the contact page is the fastest route.

FAQ

Related questions

Why does a website show the wrong country even though my proxy IP is correct?

The site may be using its own geolocation database, which can disagree with the one you checked, or it may be reading another signal such as a stored cookie, your Accept-Language header, the browser time zone or an account setting. Clear cookies, align locale and time zone with the exit country, and check what the site itself reports.

How accurate is IP geolocation at the city level?

Much less accurate than at the country level. City results are estimates with an error radius that can span tens or hundreds of kilometers, and mobile carrier addresses often resolve to a gateway city far from the device. Treat city as a hint and verify against the target's own behavior.

Can I fix a wrong geolocation for my own IP range?

Yes, if you operate the range. Publish a geolocation feed in the RFC 8805 format and submit corrections to the major geolocation database providers, most of which have a correction form. Changes can take weeks to propagate to every service that uses their data.

Do I need to use a proxy to look up an IP's location?

No. Once you know the exit IP, you can query geolocation services for that address directly from your own network. That avoids paying proxy bandwidth for lookups and avoids a rotating endpoint handing you a different address for each lookup.

Run it on a network you can account for

Order from 1 GB or 1 IP with no monthly minimum, or talk to an engineer about your workload first.

One business day, from a named engineer.