Skip to main content
Explainer · developers

Geo-IP gotchas for developers

Geo-IP libraries make "what city is this user in?" look like a one-line answer. It is not — and treating the result as ground truth is the bug that follows every team that ships something IP-aware.

The premise that breaks: "IP → location" is a function

Geo-IP libraries present a clean API — give me an IP, get back a country, region, and city. Engineers who haven't worked with the data before reasonably assume that the answer is right, the same way Math.sqrt is right. It's not. Geo-IP is a stack of statistical approximations built from sources of varying reliability, and the precision claimed by vendor marketing is much higher than the precision the data actually delivers for any specific lookup.

This article catalogs the gotchas the way you'll meet them in production — the ways the obvious code path silently produces wrong results, and the small changes that make the wrongness handle-able instead of catastrophic.

How geo-IP databases are actually built

Major providers (MaxMind, IPinfo, DB-IP, ip2location) ingest data from a few core sources and combine them:

The combination is good enough for "what country is this user in" most of the time. It is much weaker for "what neighborhood is this user in," and it is essentially noise for "what specific address is this user at" outside of dedicated enterprise connections.

The gotchas, in order of how often they bite

1. Mobile carrier IPs lie about location

A user on T-Mobile in Boston may have an IP that geo-IP databases place in Bellevue, Washington, or Atlanta, Georgia, or somewhere else entirely. The reason is that mobile carriers route customer traffic through a small number of national packet gateways; the IP-to-location mapping points at the gateway, not the customer's cell tower. For mobile traffic, country is usually right; sub-country location is frequently wrong by hundreds of miles.

2. CGNAT pools cross city boundaries

When a residential ISP NATs hundreds of customers behind one shared public IP, the geo database has to pick one location for the shared address. The picked location is wrong for most of the pool. This becomes increasingly common as ISPs migrate residential customers to CGNAT. The honest answer for these IPs is "somewhere in this ISP's footprint" — but the API gives you one city anyway.

3. Corporate VPNs and cloud egress

An employee working from home through a corporate VPN appears to come from the VPN concentrator's IP, which is wherever the company put it (often their HQ city, often a different country from the employee). A workload running on AWS appears to come from us-east-1, regardless of who triggered it. Both are common in B2B traffic and routinely cause "why does the system think I'm in Virginia" support tickets.

4. CDN edges and forward proxies

If your application sits behind a CDN, what you see in the request is the CDN edge's IP unless you read the X-Forwarded-For or CF-Connecting-IP header set by the CDN. Forgetting to handle this consistently means you geolocate the wrong IP for the entire traffic load. The worse failure mode is partial handling — some endpoints read the forwarded header, others read the socket peer; the same user gets different geo results from different endpoints in your system.

5. The database is older than you think

Open-source GeoLite2 is updated weekly; commercial GeoIP2 is updated daily; IPinfo and DB-IP similarly. Most teams download once and never refresh. A range that the ISP reassigned six months ago is still mapped to the old location for everyone using your snapshot. Set a cron to download fresh data weekly and verify the install date in your monitoring.

6. Anycast and the "where is 1.1.1.1?" problem

Many widely-used IPs are anycast — announced from hundreds of physical locations via BGP, with users connecting to whichever is closest. A geo lookup of1.1.1.1 can return Los Angeles (the registrant's address), Cloudflare's headquarters, or no answer at all, depending on the provider's handling of anycast ranges. There is no single "location" for these IPs. Most geo-IP databases flag the prefix as anycast; few applications read the flag.

7. IPv6 coverage is worse than IPv4

Geo-IP databases have less data on IPv6 ranges. Coverage is increasing each year but coverage gaps are larger than IPv4. If your application is dual-stack and you geolocate the request IP, expect a meaningful percentage of v6 lookups to return country-only or "unknown."

8. Travelers have permanent mismatch

A French user on vacation in Japan has a Japanese IP. Your default-language logic might serve them Japanese content; your currency converter might switch to JPY; your compliance might apply Japanese rules. All defensible for a casual visitor, all wrong for the user who's logged into their French account. The right defense is to let logged-in users override geo-based defaults from their profile.

How to code defensively around geo-IP

Treat geo as a hint, not a fact

For UX defaults (language, currency, date format), use geo-IP as the initial guess and let the user change it. Persist the user's choice; never re-guess on subsequent visits if they've expressed a preference.

Layer signals for hard gates

For legal/compliance gates (age verification, regional content blocks, payment method restrictions), don't rely on IP alone. Combine with: explicit user-declared country, billing address, phone number country code, KYC documents where they exist. Each adds another signal that makes the combined accuracy actually acceptable.

Handle the unknown-location case explicitly

A meaningful fraction of lookups return "country unknown" or low-confidence. Your code should have an explicit branch for this rather than treating it as the default country. The right UX is usually "ask the user."

Distinguish residential / hosting / VPN

Geo-IP vendors increasingly publish a "connection type" alongside the geo data. Connections from datacenter ASNs, known VPN ranges, or anonymizer exit nodes behave differently from residential ones. Logging this is the difference between "block all traffic from this country" (legitimate users hate it) and "require extra verification for traffic from anonymizer ranges" (often the right policy).

Show your work to the user

If your application makes a noticeable decision based on geo-IP (rate limit friendly, content gate, currency switch), tell the user that "we're treating you as a visitor from {country}; click here to change." This way, when geo-IP is wrong — and it sometimes is — the user has an immediate path to correct it. The alternative is a frustrated support ticket about a setting they can't see.

Honor X-Forwarded-For correctly

If your application is behind a load balancer, CDN, or reverse proxy, the peer-socket IP is the proxy's. The real client IP is in X-Forwarded-For (a comma-separated list, with the first entry being the original client) or in a CDN-specific header like CF-Connecting-IP. Configure your framework's trusted-proxy list explicitly — naive handling either trusts spoofed headers from clients (insecure) or fails to extract the real IP (geo always wrong).

Validating your setup

Build a small admin page that geolocates the inbound request IP and displays: the IP your application sees, the IP from X-Forwarded-For, the geo result, and the connection type. Spot-check from a VPN, from your phone on cellular, from a CI runner, and from a coffee shop's Wi-Fi. The mismatches are your edge cases; the cases where they match are your common path.

IPFerret can stand in for that admin page when you don't want to build one. Hit https://ipferret.com/api/lookup from your application's point-of-view and compare what we say to what your provider says.

Related reading