← field notes

How your phone knows a wifi has no internet

Connect to a hotel or airport wifi and the phone usually knows within a second or two that something’s wrong. Before any app has tried to load a page, before the user has opened a browser, the operating system already has an opinion: this network is fake, or at least gated. That opinion comes from a tiny, unglamorous background request that runs on every network join, and it’s worth understanding because the same mechanism shows up constantly in the infrastructure that fights bots, fingerprints devices, and keeps proxy pools honest.

The walled garden problem

A captive portal is a network that intercepts traffic until a person logs in, accepts terms, or pays. The router or gateway hijacks any outbound request and redirects it to a login page, regardless of what the device was actually trying to reach. From the network’s point of view this is normal and expected. From the device’s point of view it looks identical to a broken connection, unless the OS has a way to tell the difference between “the internet is unreachable” and “the internet is reachable but something is standing in the way.”

That distinction matters because a phone can’t just try the user’s actual traffic and see what happens. If it waited for a real app request to fail, the user would already be staring at a spinner. So every major OS runs a dedicated, silent probe the moment it joins a network, before anything else happens.

What your phone actually asks

The trick is a request to a URL the OS controls, with a response it already knows by heart. iOS and macOS ask for captive.apple.com/hotspot-detect.html and expect back a tiny HTML page containing the word “Success,” nothing else. Android and ChromeOS ask Google’s connectivitycheck.gstatic.com/generate_204 and expect an HTTP 204 No Content, an empty response with no body at all. Windows asks Microsoft’s www.msftconnecttest.com/connecttest.txt and expects the literal string “Microsoft Connect Test.”

The logic is simple. If the request returns exactly the expected response, the OS decides the network has open internet access and does nothing further. If the request returns anything else, a redirect, a login page, a different status code, the OS concludes there’s a captive portal in the way and pops up its own mini-browser to handle the login, separate from whatever app the user actually opened. If the request times out entirely or the DNS lookup fails, the OS decides the network is simply dead, no wifi icon exclamation mark needed, just “no internet available.”

Three vendors, three URLs, three exact expected payloads, but the same underlying idea: probe with a known-good request and treat any deviation as a signal, not an error.

Why a plain 200 isn’t good enough

It’s tempting to think any successful HTTP response would do the job, but the specificity is the point. A captive portal gateway almost always answers every request with a 200 and a page, because that’s how it serves the login form. If the OS only checked “did I get a response,” every captive network would look identical to open internet, which is the opposite of what it needs to know. Checking for an exact, minimal, hard-to-fake body is what lets the OS separate “here’s your login page” from “here’s the internet.”

This is also why some genuinely open networks trip false positives. A hotel network with an ISP-side DNS sinkhole, a corporate proxy that rewrites all traffic, or a broken transparent proxy can all intercept that one specific probe request even when the rest of the internet works fine, and the phone will insist there’s a portal that doesn’t exist. The reverse happens too: some captive portals whitelist the exact probe URLs so the OS sails through checking, then block everything else, which is why “connected, no internet” banners still show up on networks that technically passed the test.

The tell in device farms and proxy pools

Anyone running a fleet of SIMs, modems, or proxy exits ends up caring about this same mechanism for a completely different reason: it’s the fastest, cheapest way to tell if a connection is actually alive. Rather than fetching a real page and parsing it, a health check can hit generate_204 or an equivalent endpoint and just look at the status code. A 204 means the exit is clean and passing traffic normally. A redirect or an HTML body where none was expected means something upstream, a carrier portal, a data cap wall, a suspended SIM notice, is intercepting traffic before it reaches the open internet.

This makes captive portal probes a convenient monitoring primitive, but it also means they’re a liability if a fleet is trying to look like a population of ordinary phones. A real device checks these endpoints unprompted, on its own schedule, using its own OS-specific URL and user agent. A stripped-down automation stack that only fetches the pages an operator cares about, and never touches connectivitycheck.gstatic.com or captive.apple.com, produces a traffic pattern with a gap in it. Network operators and anti-fraud systems that log DNS and connection patterns can notice that absence just as easily as they’d notice a suspicious presence.

Captive portal checks as a fingerprint

The specific set of URLs a device queries, and the order and timing it queries them in, doubles as a weak but real signal of what that device actually is. A request to msftconnecttest.com strongly implies a Windows machine. A request to captive.apple.com implies Apple hardware or software claiming to be Apple hardware. If a connection presents itself as an iPhone in its browser user agent but the network-level traffic never shows the Apple captive check, or shows the Android one instead, that’s a mismatch worth flagging. It’s the same category of inconsistency that trips up emulator detection more broadly: the parts of a device that operate below the app layer are harder to fake consistently than the parts a script can set with a single header.

None of this makes captive portal probing a reliable standalone detector. Plenty of legitimate devices sit behind corporate firewalls that block these exact domains, and plenty of privacy-conscious users or MDM profiles disable the check entirely. It’s one weak signal among many that fraud and bot detection systems fold into a larger fingerprint, not a single tripwire that flags a device on its own.

When detection breaks quietly

The captive portal check also fails in ways that have nothing to do with fraud. A misconfigured DNS-based ad blocker can swallow the probe domains along with the ads, leaving a phone stuck insisting it has no internet when it plainly does. A VPN or proxy that only routes some traffic can strand the captive check on the wrong path, so the OS forms an opinion based on a route the rest of the device isn’t actually using. Anyone who’s debugged a “connected but no internet” phone on a network that clearly works has usually run into one of these mismatches, where the probe and the real traffic disagree about the state of the world.

The cat and mouse

What makes this small mechanism worth a closer look is how it sits at the intersection of two very different worlds. To a network engineer it’s a convenience feature, a way to get a login page in front of a user without them having to guess why their phone can’t load anything. To anyone building or defending against automated traffic at scale, it’s a low-cost fingerprint, a health check, and an occasional false alarm, all coming out of the same three-line HTTP request. Neither side designed it with the other in mind, which is usually when the most interesting detection signals turn up.

If you want more of how the internet’s plumbing gets read, tested, and gamed from the inside, The Hidden Internet covers the rest of it.

Get new guides and videos first — join the Telegram channel.

watch
The Hidden Internet on YouTube

Every field note starts as a short documentary. Watch the systems in this piece explained on screen.

subscribe →
read on
More field notes

The rest of the series on how the modern internet detects, tracks, and sorts the traffic that reaches it.

browse the archive →