L
o
a
d
i
n
g
.
.
.
https://michele.zonca.org

networking

How captive portals work, and what is actually standardized

By Michele Zonca

#networking

#wifi

#security

19 September 2026

11 minutes to read

September 19, 2026

Joining an open Wi-Fi network in a hotel, an airport or a café usually ends the same way: a login page opens on its own, you accept the terms (or type a room number or a code), and the network starts working. Two questions about it: how does the device know it has to show that page, and is any of it a standard protocol?

The short answer is that, for most networks in use today, it is not one protocol. It is a set of conventions that grew around each other, and only in 2020 did the IETF publish a standard way to handle the discovery part. The login itself is still just a web page, and no standard says what it contains.

The flow most networks still use

  1. The device joins an open SSID. There is no authentication at the Wi-Fi layer and, unless the network uses OWE, no encryption either.
  2. DHCP assigns an address, a gateway and a DNS server. As far as layers 2 and 3 are concerned, the device is connected.
  3. The gateway (a firewall, a wireless controller, or captive portal software such as Nodogsplash) keeps the device in an unauthenticated state. Traffic is blocked except DHCP, DNS, the portal itself and, often, a short list of allowed hosts usually called a walled garden.
  4. Right after connecting, the operating system sends a plain HTTP request to a URL run by its vendor and compares the answer with the one it expects.
  5. The gateway intercepts that request, either by answering with a redirect to the portal or with its own page, or by making DNS resolve every name to the portal. The answer does not match, so the OS concludes it is behind a captive portal.
  6. The OS shows the login page. Apple devices open a small browser sheet (the Captive Network Assistant), Android prompts the user to sign in, and Windows opens a browser, which Microsoft documents as by design.
  7. The user submits the form. The portal then has to tell the gateway to authorize that client, and how it does that depends on the product: a link back to the gateway, a RADIUS exchange, an API call to a controller. The gateway starts forwarding the client’s traffic, usually for a limited time.
  8. The OS repeats the probe, gets the expected answer, and marks the network as online.

Each vendor picked its own probe URL:

  • Android: http://connectivitycheck.gstatic.com/generate_204, expecting 204 No Content. A redirect means captive portal, according to the Android 11 documentation.
  • Apple: http://captive.apple.com/hotspot-detect.html, expecting a page whose body is Success.
  • Windows: http://www.msftconnecttest.com/connecttest.txt, expecting the text Microsoft Connect Test. Before Windows 10 1607 it was www.msftncsi.com/ncsi.txt, plus a DNS query for dns.msftncsi.com expecting 131.107.255.255.
  • Firefox: http://detectportal.firefox.com/success.txt, expecting success.
  • NetworkManager on Ubuntu: http://connectivity-check.ubuntu.com/, expecting 204 with the header x-networkmanager-status: online (this is what /usr/lib/NetworkManager/conf.d/20-connectivity-ubuntu.conf points to on my machine).

From a normal connection they all return exactly what the OS wants:

$ curl -si http://connectivitycheck.gstatic.com/generate_204 | head -1
HTTP/1.1 204 No Content
$ curl -s http://captive.apple.com/hotspot-detect.html
<HTML><HEAD><TITLE>Success</TITLE></HEAD><BODY>Success</BODY></HTML>
$ curl -s http://www.msftconnecttest.com/connecttest.txt
Microsoft Connect Test

A captive portal works by breaking that equality: any other status or body means the network is not letting the request through.

The probes are plain HTTP on purpose. To intercept an HTTPS request the gateway would have to answer for a host it does not own, and the client would reject the certificate. RFC 6585 says the same about its 511 status code. It is also why browsing to an HTTPS site before logging in gives a certificate error or a connection failure instead of the portal. Any plain HTTP URL normally triggers the redirect, so opening one of the probe URLs, or a site such as neverssl.com, is the usual way to get the login page when the OS does not show it.

Why this is fragile

Gateways depend on the probes. The Nodogsplash README puts it this way: “All modern mobile devices, most desktop operating systems and most browsers now have a Captive Portal Detection (CPD) process that automatically issues a port 80 request on connection to a network. Nodogsplash detects this and serves its ‘splash’ web page.”

  • There is no standard URL. Every vendor runs its own, and the gateway has to intercept all of them correctly. The Android documentation lists this among the problems of probing: cleartext HTTP requests can be allowed or blocked by mistake instead of being redirected.
  • The device gets no explicit signal. It infers captivity from a mismatch, so anything else that alters the probe can give a wrong result. Microsoft’s NCSI FAQ lists forced VPN tunneling, proxies and third-party software among the things that interfere.
  • Interception damages other traffic. RFC 8952 describes these systems as forging DNS or HTTP responses and sometimes proxying HTTPS, which breaks DNSSEC validation, prevents secure TLS operation and only works reliably with browsers. An application that is not a browser just sees failures, with no way to know that the network wants a login.

What is standardized

The IETF CAPPORT working group published three RFCs in 2020:

  • RFC 8910: the network advertises a URI in DHCPv4 option 114, DHCPv6 option 103 or IPv6 Router Advertisement option 37. It obsoletes RFC 7710, which used DHCPv4 option 160 and conflicted with existing use by Polycom devices.
  • RFC 8908: that URI points to a Captive Portal API. The client makes an HTTPS GET with Accept: application/captive+json and receives JSON.
  • RFC 8952: the architecture that ties them together: the user equipment, a provisioning service (DHCP or RA), the API server, the enforcement device that blocks or allows traffic, and an optional signal that the state has changed.

The exchange looks like this:

GET /captive-portal/api/X54PD39JV HTTP/1.1
Host: example.org
Accept: application/captive+json
{
  "captive": true,
  "user-portal-url": "https://example.org/portal.html"
}

captive is the only required field. The optional ones are user-portal-url, venue-info-url, can-extend-session, seconds-remaining and bytes-remaining.

With this in place the flow changes. The DHCP lease already carries the API URI, the device fetches its state over HTTPS (the enforcement device has to allow that before login), sees captive: true and opens user-portal-url. When the user is done, the API returns captive: false. No probe and no interception are needed, and the device can also learn when the session ends.

Android 11 fetches the API right after connecting when option 114 is present and falls back to probing otherwise. iOS 14 and macOS Big Sur support the same options, and Apple’s developer note asks network operators to move from interception to them. I did not check how many gateways in the field advertise option 114, so on a given venue network I would still expect the older flow.

What the standard does not cover is the login. user-portal-url is an ordinary web page, and nothing standardizes what it asks for: a checkbox, a room number, a voucher, a payment, a social account. Each product does that in its own way.

There is also HTTP status 511, Network Authentication Required, defined in RFC 6585 for intercepting proxies. The probes above do not look for it, they compare the answer with the one they expect.

  • WISPr is an older approach for clients that log in without a person: the portal page carries XML inside an HTML comment, a smart client reads it to find where to submit credentials, and RADIUS handles the back end. It was a draft submitted to the Wi-Fi Alliance, and the specification is no longer available from them.
  • Passpoint (Hotspot 2.0) Release 3 moves terms acceptance to the Wi-Fi layer: the access point signals that terms must be accepted, the device shows a notification with an HTTPS URL, and the network can be a secured one instead of an open SSID with interception. Android documents support since Android 12 and disconnects from the network if the URL is not HTTPS. I did not check other operating systems.

Two security notes

After the login, the gateway has to recognize the device on every packet. RFC 8952 discusses the options: the IP address, which is unique on a working network but can be spoofed, and the MAC address, which it says “is often used as an identifier in existing implementations”. That interacts with private Wi-Fi addresses. Apple devices use a different MAC address for each network, either fixed or rotating every two weeks, and a new one after network settings are reset. A portal that keys the session on the MAC address sees a new client when that happens and shows the login page again. Other operating systems have a similar option.

The other note is trust. A captive portal is an on-path device by design, so anyone can run an open SSID with the same name and a login page of their own. The standard helps with part of it: the API must be reached over HTTPS with a valid certificate, so the device can check who it is talking to. The URI itself still arrives through DHCP or RA, and RFC 8952 says the network should deliver it securely, with the DHCPv6 AUTH option as an example, which is hard to do on a network where anyone can join without credentials.

Summary

What appears on the phone is a probe over plain HTTP plus interception by the gateway, a convention that every vendor implemented separately. Since 2020 there is a standard way to tell the device that the network is captive and where to log in (RFC 8910, 8908 and 8952), supported by Android 11 and iOS 14 or later, and it coexists with the older flow. The login page itself remains outside any standard.