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:
- RIR allocation records. ARIN, RIPE, APNIC, LACNIC, AFRINIC each publish records of which organization holds which block. Useful for country-level mapping; useless for city-level because the registrant address is often the company's HQ, not the customer's location.
- BGP routing announcements. Which ASN currently originates which prefix, and where that ASN's points-of-presence physically are. Useful for inferring "this prefix probably serves the US East Coast" but not "this exact IP is in Boston."
- Direct ISP feeds. Some large ISPs sell their subscriber-region mappings to geo-IP vendors. This is the only source that produces real address-level precision, and it's a small fraction of the data.
- End-user telemetry. When a user grants browser geolocation permission and the result is correlated with their IP, the vendor learns "IP X was at lat/lon Y at time T." Aggregated over millions of consenting users, this is how geo-IP databases get city-level data for residential ranges they wouldn't otherwise see.
- Hostname inference. Reverse-DNS hostnames frequently encode city codes (
ny-edge-01.isp.example). The vendor parses these to refine mappings. - WHOIS / RDAP contact addresses. Same caveat as RIR records — the address is the organization's, not the connection's.
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
- What is my IP? — the fundamental concept geo-IP is built on top of.
- CGNAT — the largest single cause of "geo-IP put me in the wrong city" complaints.
- WHOIS / RDAP — see the registrant address behind any IP block.
- ASN explorer — the network operator metadata most geo-IP libraries include alongside the location.
