DEV Community

Cover image for What Is CGNAT and Why It Breaks IP Geolocation
ABDULLAH AFZAL
ABDULLAH AFZAL

Posted on

What Is CGNAT and Why It Breaks IP Geolocation

Carrier-grade NAT (CGNAT) lets one public IPv4 address stand in for hundreds of people at once. It's why a phone in one town can geolocate to a city 200 km away, and why a rate limit aimed at one abuser can lock out a crowd of mobile users who did nothing.

Most code that touches IP data quietly assumes one IP is one place and one IP is one user. CGNAT breaks both. A 2026 measurement study of four geolocation databases put the median error for mobile connections at 179 to 207 km. On fixed broadband, the same databases were off by 3 to 16 km.

TL;DR

  • CGNAT is NAT run by your ISP or mobile carrier, so many unrelated customers share one public IPv4 address. The carrier side uses 100.64.0.0/10, the shared address space reserved by RFC 6598.
  • On mobile networks, the public IP reflects the carrier's gateway, not the phone. Median geolocation error was 179 to 207 km on mobile versus 3 to 16 km on fixed lines.
  • Country-level lookups still hold: fewer than 1% of observations landed in the wrong country.
  • Cloudflare found CGNAT addresses get rate-limited three times more often than other IPs, even though they carry proportionally less bot traffic.
  • On a CGNAT IP, trust the country, treat the city as a hint, and never treat the address as a user ID. CGNAT exists because IPv4 ran out, and it isn't going away soon. If your app geolocates, rate-limits, or bans by IP, assume a good part of your mobile traffic arrives through shared addresses. Country decisions survive that. City decisions and per-IP identity are where things go wrong.

What is CGNAT?

CGNAT (carrier-grade network address translation, also called large-scale NAT) is address translation performed inside the ISP's network rather than only on your home router. The carrier gives each customer a non-routable address, then maps traffic from many customers onto a small pool of shared public IPv4 addresses before it reaches the internet.

The usual setup stacks two translations. Your router maps 192.168.1.x devices to its WAN address, which under CGNAT is also non-routable, so the carrier translates it again into a public address other customers share. Engineers call this NAT444, after the three address realms involved.

Carriers do it because IPv4 has about 4.3 billion addresses and the regional registries had run through their free pools by the early 2010s, as Cloudflare's write-up on CGNAT detection explains. With IPv6 rollout slow, CGNAT lets an operator keep adding subscribers on addresses it already holds.

CGNAT vs NAT

Home NAT CGNAT
Who runs it Your router Your ISP or mobile carrier
Who shares one public IP Devices in one household Many unrelated customers
Port forwarding Works Not possible from your own router

Only the right column is a problem for your app. Home NAT puts a family behind one IP. CGNAT puts strangers behind one IP, and they don't have to live in the same city.

The CGNAT IP range: 100.64.0.0/10

RFC 6598 (April 2012) reserved 100.64.0.0/10, about 4.2 million addresses from 100.64.0.0 to 100.127.255.255, as "Shared Address Space" between a carrier's NAT and customer equipment. The RFC 1918 ranges were off the table because subscribers already use 10.x and 192.168.x at home, and the same address on both sides of a router breaks things.

If your router's WAN address falls anywhere from 100.64 to 100.127, you're behind CGNAT.

How one public IP ends up serving hundreds of users

The carrier's NAT tells customers apart by port, not by address. Every outbound connection gets a port on a shared public IP, and the NAT remembers which subscriber owns which port. One IPv4 address has 65,535 ports to hand out, so it can carry many households' connections at the same time. Cloudflare puts the count behind a single IPv4 address at hundreds, and sometimes thousands, of users.

Mobile networks add distance to the sharing. Your phone's traffic doesn't touch the public internet at the cell tower. The carrier carries it across its own network to a gateway, and that's where it gets a public source address. The IAB's workshop report on IP address geolocation describes the effect directly: a device can be in one place while its traffic exits through a gateway in a different region.

The sharing isn't spread evenly. Cloudflare's analysis found the highest ratios of internet users to IP addresses across large parts of Africa and South Asia, and the lowest in Australia, Canada, Europe, and the US. African networks also showed the most distinct user agents per CGNAT address. The regions with the fewest IPv4 addresses squeeze the most people behind each one.

It isn't only a mobile problem. Cloudflare describes CGNAT across broadband ISPs as well as mobile networks, and the IAB report lists satellite networks alongside mobile CGNAT among the biggest challenges for publishing accurate location data.

Why mobile users geolocate hundreds of kilometers away

An IP geolocation database maps address ranges to places. For a CGNAT range, the only stable fact about the address is where the carrier's gateway sits, so that's what ends up in the database. The IAB report puts it in one line: geolocating the IP "identifies the gateway, not the user."

The Lost in the Prefix study measured how far that drifts. Researchers at Virginia Tech compared four databases against 37,302 observations with known locations, drawn from RIPE Atlas probes and UNICEF Giga school connectivity tests across 175 countries. Median errors:

Database Mobile networks Fixed networks
IPinfo 179 km 8 km
IP2Location 186 km 3 km
MaxMind GeoLite2 204 km 13 km
DB-IP 207 km 16 km

The authors trace it to prefix granularity. About 70% of the database prefixes covering mobile networks span more than 100 km, so one database row has to pick one city for people spread across a region. In Africa, 66 to 72% of lookups missed by more than 100 km. In Asia, 53 to 61% did. Europe stayed between 9 and 20%.

The finding I'd underline: all four databases failed at similar rates on mobile, and the paper concludes that switching providers doesn't get you out of it. The MaxMind entry is GeoLite2, the free tier, so it isn't MaxMind's best data. Still, four independently built datasets agreeing on the same misses points at network structure, not at one vendor's sloppiness.

The IAB report adds a nastier edge case: phones near a national border whose IP geolocation keeps flipping between two countries.

Subscribers with 100.64 addresses share one public IP, so lookups return the gateway city and rate limits see one user

What still works: the country

Wrong-country results were rare in the same study, at fewer than 1% of observations per provider. That makes IP-derived country useful for things like currency defaults, content catalogs, and other coarse regional decisions even where city accuracy falls apart. For tax, compliance, or legal-jurisdiction decisions, treat the IP country as one input rather than proof of where the person legally resides or which rules apply.

One IP is not one user

Location is the visible failure. Identity is the quieter one.

Cloudflare built a classifier to spot CGNAT addresses across its network, then looked at how customer security rules treated them. CGNAT IPs were rate-limited three times more often than non-CGNAT IPs. Their traffic wasn't especially bot-heavy, either: the mean bot rate was 7% for CGNAT addresses against 13.1% for everything else. Real people got throttled because they happened to share an address.

The same mistake shows up in ordinary app code:

  • Per-IP signup or trial caps. "Three accounts per IP" quietly becomes "three accounts per carrier gateway" for mobile users.
  • IP bans. Banning one abuser's mobile IP bans everyone currently behind it, and the abuser gets a different address the next time their connection is reassigned.
  • Per-IP login throttling. A credential-stuffing defense keyed only on IP can lock out unrelated users who share the address.
  • Impossible-travel rules. A user whose phone IP geolocates 200 km from their home broadband IP an hour later hasn't gone anywhere.
  • Unique-visitor counts. Counting distinct IPs undercounts people on mobile networks. An IP address was never a user ID. It was a convenient approximation that held up while one household got one address. CGNAT just made the shortcut visible.

How to tell if an IP is behind CGNAT

From the user's side

Check your router's WAN address. Anything from 100.64.0.0 to 100.127.255.255 means your ISP is doing CGNAT.

A traceroute tells the same story from the other direction. Cloudflare's detection pipeline treats a 100.64.0.0/10 hop between your first private hop and your public IP as a strong CGNAT indicator, and it ran that test from more than 9,000 RIPE Atlas probes.

From your server

Nothing in an HTTP request says "this is CGNAT." You infer it from signals:

  • The IP's ASN belongs to an ISP or mobile carrier, not a hosting or cloud provider.
  • Reverse DNS sometimes gives it away. Cloudflare matched PTR records containing cgnat, cgn, or lsn.
  • One IP shows a wide spread of user agents and devices over a day. That diversity is one of the strongest signals in Cloudflare's classifier.
  • Your geolocation provider labels the connection as mobile. That last one is where I'd start, because it's a single field. Disclosure: I work on ipgeolocation.io, so it's the provider I know best. On paid plans, its IP Geolocation API returns network.connection_type and asn.type, and include=geo_accuracy adds an accuracy_radius in kilometers plus a confidence of high, medium, or low. When I looked up ranges from T-Mobile USA, Verizon, AT&T, and Safaricom on October 13, 2026, all of them came back with connection_type: "Mobile" and asn.type: "ISP". Not every range carries the label: two other carrier ranges came back with an empty value. Read "Mobile" as a positive signal and an empty string as unknown, not as proof of a fixed line.

Read the radius carefully. The T-Mobile range came back in Fort Worth, Texas, with a 4.9 km radius and high confidence. That's the provider's estimate for the range. It can't tell you whether the phone using that address right now is in Fort Worth or 200 km away, because nothing upstream of the carrier's gateway is visible from outside the carrier. On a mobile range, a tight radius means "the gateway is here," not "the user is here." So treat the connection type as the switch: once a lookup says mobile, apply the country-yes, city-maybe rule whatever the radius says.

What your app should trust on a CGNAT IP

Here's how I'd split decisions when the traffic might be shared:

Use case Safe on a CGNAT IP? Do this instead
Currency, pricing region, content catalog Yes Use the country
Tax or legal jurisdiction Not by IP alone Use IP country as supporting context and confirm with the authoritative account, billing, or residency information your policy requires
City-level personalization As a hint only Fall back to region, or let the user pick
City-level geofencing No Use device location with consent
Distance and impossible-travel rules Not on mobile Relax thresholds for mobile connections, or skip the check
Rate limiting Not by IP alone Key on account, session, API token, or device
Bans and blocklists Avoid on carrier ranges Ban the account or device
Unique users in analytics No Count sessions or accounts

The pattern is short. Anything that needs a country is fine. Anything that needs a city or a person needs a second signal.

That 100.64.x.x address in your logs

If a 100.64.x.x address turns up where you expected a client IP, it isn't anyone's public address. The usual causes:

  • You read the wrong X-Forwarded-For entry and grabbed an internal hop.
  • Overlay networks such as Tailscale assign device addresses from 100.64.0.0/10, so traffic from inside a tailnet carries them.
  • Your own infrastructure, or a provider's, uses the range internally. A geolocation lookup won't rescue it. ipgeolocation.io answers 100.64.1.1 with HTTP 423 and a message that it's a bogon address, which is the right answer: there's no location to give.

The obvious filter for this is if ip.is_private: skip. That lets CGNAT addresses straight through, because Python's ipaddress module returns False for is_private on 100.64.0.0/10. Its documentation calls out the exception: that range is neither private nor global. Check is_global instead.

import ipaddress
import logging
from typing import Optional

logger = logging.getLogger(__name__)


def client_ip(xff_header: Optional[str], remote_addr: str, trusted_hops: int = 1) -> Optional[str]:
    """Return the client IP only when the app is reached through the expected proxy path.

    This hop-count approach is safe only when every request reaches the app through
    exactly trusted_hops proxies and clients cannot connect to the origin directly.
    If your topology has multiple paths, validate proxy IPs/CIDRs instead.
    """
    chain = [hop.strip() for hop in (xff_header or "").split(",") if hop.strip()]
    chain.append(remote_addr)

    if len(chain) <= trusted_hops:
        return None

    candidate = chain[-(trusted_hops + 1)]
    try:
        addr = ipaddress.ip_address(candidate)
    except ValueError:
        # "unknown", "ip:port", or garbage. Don't guess at a location.
        logger.debug("Unparseable client IP %r in X-Forwarded-For", candidate)
        return None

    # Some stacks hand you ::ffff:100.64.1.1 instead of the IPv4 form.
    if isinstance(addr, ipaddress.IPv6Address) and addr.ipv4_mapped:
        addr = addr.ipv4_mapped

    # is_private is False for 100.64.0.0/10. is_global rejects shared
    # address space along with RFC 1918, loopback, and link-local.
    return str(addr) if addr.is_global else None
Enter fullscreen mode Exit fullscreen mode

It returns None instead of guessing, so the caller can skip the lookup and fall back to defaults. That's fail-open by design: a missing location should never block a request. Set trusted_hops only when every request follows the same proxy path and the origin cannot be reached directly. Too low and you geolocate your own load balancer; too high, or allowing a direct path to the origin, can make you trust an address the client supplied. Where possible, configure your framework or proxy layer with explicit trusted proxy IPs/CIDRs instead of parsing X-Forwarded-For yourself.

Where this leaves you

If you run a network, the fix that helps everyone downstream is a geofeed: an RFC 8805 file that maps each regional address pool to where it actually reaches the internet. Our position paper for the IAB workshop argued for region-specific entries on large prefixes and a standard attribute for the last-mile connection type (mobile, fiber, satellite, VPN), so consumers can tell a gateway estimate from a real location. If you only consume IP data, decide per feature whether you need the city at all. Most of the time the country is enough, and the country is the part CGNAT leaves alone.

Top comments (0)