How Accurate Is IP Geolocation? We Tested 2,852 IPs

We checked 2,852 RIPE Atlas probes with known locations against our IP lookup. Country: 99.4%. Median error: 13 km. One in five off by 100 km or more.

  • 7 min read
  • Published February 26, 2026
  • Updated October 22, 2026
Article featured image

IP geolocation gets the country right almost every time and the city right a little over half the time. We measured it this October: 2,852 internet connections with known locations, looked up through our IP Lookup API. The country matched for 99.4% of them. The median distance between where the lookup put a connection and where it really was came to 13 km, and about one lookup in five landed 100 km or more away.

The averages hide where it goes wrong. In France, the database put 116 of 281 test connections in central Paris, most of them nowhere near it.

How we measured

The ground truth came from RIPE Atlas, a network of small measurement devices (“probes”) that volunteers plug into their own connections, and each probe’s public IPv4 address and location are published through RIPE’s API.

We took every public, connected probe tagged as a home connection: 2,893 probes, of which 2,852 had both an IPv4 address and a location. We looked up each address with our IP Lookup API, which is built on MaxMind’s GeoLite2 City database, and computed the great-circle distance between the coordinates it returned and the probe’s published position. One request failed, leaving 2,851.

Probe hosts set their own locations, so a few are wrong or out of date. The sample leans heavily on Europe (1,942 of the 2,851) and on the kind of person who hosts a network probe, which means fixed home broadband, not phones. And this measures one database, the free GeoLite2. MaxMind sells a more precise one; we didn’t test it.

The results

Share of 2,851 lookups
Country correct99.4%
Within 10 km44.5%
Within 25 km59.9%
Within 50 km68.5%
Within 100 km77.9%
More than 250 km off11.2%
No city returned9.5%

The median error was 13.2 km. The 90th percentile was 283 km.

The 17 country misses are mostly places where a network crosses a border: probes in Russia whose addresses resolve to Germany, the Netherlands or Sweden, Swiss probes landing in Germany, Italy and Finland, a probe in Kenya on an address registered in Mauritius. A few are clearly the probe being somewhere other than its owner said. A probe listed in Mexico on a UK broadband address is more likely sitting in the UK.

Where the errors come from

Country is reliable because address blocks are allocated to networks that operate in a country. City is a guess built from where a network’s customers usually are, and some networks hide that completely.

France shows it most clearly. Of 281 French probes, 117 came back with no city at all, and the database placed 116 of them in central Paris (at two points a kilometre apart). Only 25 of the 117 were actually within 30 km of Paris. The French probes that did get a city had a median error of 10 km. The ones parked in Paris had a median error of 306 km.

The UK and Italy show a softer version of the same thing. UK probes resolved to London boroughs (Westminster, Islington, Kensington, the City) far more often than the population would explain, and the median error for UK probes was 73 km. Italian probes piled up in Rome and Milan, with a median error of 95 km. Germany (11 km over 445 probes) and the US (9 km over 423) did much better.

When the database knows nothing below country level it returns the country’s middle. For the US that’s 37.751, -97.822, a point in Kansas, with an accuracy radius of 1,000 km:

{ "ip": "8.8.8.8", "country": "US", "city": null,
  "coordinates": [37.751, -97.822], "accuracyRadius": 1000 }

Code that plots that point on a map, or computes “distance to nearest store” from it, is computing distance from a field in Kansas.

Some addresses have no location at all. 1.1.1.1 is Cloudflare’s DNS resolver, and it answers from hundreds of cities at once (that’s what anycast means), so there is no honest single location to give. Our lookup returns country: null for it. In an audit this October it looked like a bug, and the obvious fix was to fall back to the country the block is registered in. For 1.1.1.1 that’s Australia, which would be wrong for every user on earth except Australians, so it doesn’t.

What the accuracy radius is worth

Every result with coordinates comes with accuracyRadius, in kilometres. Treat it as a rough confidence band; it doesn’t bound the error.

Stated radiusLookupsReal error inside the radiusMedian real error
5 km57759%4 km
10 km39661%8 km
20 km68266%10 km
50 km35453%45 km
100 km39957%81 km
200 km34149%214 km

The real error was inside the stated radius between half and two-thirds of the time, at every size. So the radius doesn’t bound the error, but it does rank it: small radii really are more accurate. For city-level results with a radius of 20 km or less, 89.5% were within 50 km of the truth. At 21 to 50 km that fell to 53%. That gap is why the code below uses 20 km as its cutoff.

Turning a lookup into a decision

This function returns the most precise level the data supports and nothing more precise. It runs on both plans: city, coordinates and accuracyRadius are paid fields, so on the free plan it settles for the region.

// locate.js: turn an IP lookup into the level of location you can actually trust
const KEY = process.env.APIVERVE_API_KEY;

async function locate(ip) {
  const res = await fetch(`https://api.apiverve.com/v1/iplookup?ip=${encodeURIComponent(ip)}`, {
    headers: { 'x-api-key': KEY },
    signal: AbortSignal.timeout(3000),
  });
  if (!res.ok) return { level: 'unknown', why: `lookup returned ${res.status}` };
  const g = (await res.json()).data;

  // Anycast and some reserved ranges have no location at all. Don't invent one.
  if (!g.country) return { level: 'unknown', why: 'no location for this network' };

  const radius = g.accuracyRadius; // km; a paid field, null on the free plan
  if (g.city && radius != null && radius <= 20) {
    return { level: 'city', country: g.country, city: g.city, radiusKm: radius };
  }
  if (g.regionName) {
    // City is a paid field, and even when present a wide radius means it's a guess.
    return { level: 'region', country: g.country, region: g.regionName };
  }
  return { level: 'country', country: g.country };
}

module.exports = { locate };

if (require.main === module) {
  (async () => {
    for (const ip of process.argv.slice(2)) console.log(ip.padEnd(16), JSON.stringify(await locate(ip)));
  })();
}

Run against Cloudflare’s resolver, Google’s resolver and an ETH Zurich server, first with a free key and then with a paid one:

$ node locate.js 1.1.1.1 8.8.8.8 129.132.19.216
1.1.1.1          {"level":"unknown","why":"no location for this network"}
8.8.8.8          {"level":"country","country":"US"}
129.132.19.216   {"level":"region","country":"CH","region":"Zurich"}

# paid plan
1.1.1.1          {"level":"unknown","why":"no location for this network"}
8.8.8.8          {"level":"country","country":"US"}
129.132.19.216   {"level":"city","country":"CH","city":"Zurich","radiusKm":5}

Pass the visitor’s real address, not your proxy’s; the signup post covers getting it right behind Cloudflare or a load balancer.

Safe uses, and the ones that hurt people

Country-level decisions are safe: tax and currency defaults, which legal notice to show, which language to suggest, licensing regions. A 99.4% hit rate still means 6 misses in 1,000, so give people a way to override, and remember that anyone on a VPN chooses their own country.

Region and city are fine for defaults a person can correct: a pre-filled city in a form, “stores near you”, the local weather on a dashboard. Use the 20 km cutoff and fall back a level when the radius is wider.

City-level location shouldn’t be used to deny anything: blocking a purchase, flagging fraud because a login is “200 km from usual”, enforcing a delivery zone. One lookup in five is 100 km off on home broadband, and in France it put 4 in 10 of our test connections in Paris. If a decision has to be right at city level, ask the device for its location, with permission, and use IP only as the first guess.

Give your software a way to act on the world.

Scaling in production?

The same key runs from your first prototype to millions of calls — on APIVerve's rails, backed by an uptime SLA.

See pricing