511 Network Authentication Required — Captive Portal
Symptoms
- Automated scripts or services fail with 511 when running from hotel, airport, or conference venue Wi-Fi networks
- HTTP requests succeed but return unexpected HTML instead of the expected API response — the HTML is a captive portal login page
- HTTPS requests fail entirely or show certificate errors because the captive portal intercepts TLS connections and presents its own certificate
- DNS queries resolve correctly (captive portal often allows DNS through) but TCP connections to any IP return the portal page
- The application appears to work after manually visiting http://captive.apple.com or similar detection URL in a browser and completing the portal login
- HTTP requests succeed but return unexpected HTML instead of the expected API response — the HTML is a captive portal login page
- HTTPS requests fail entirely or show certificate errors because the captive portal intercepts TLS connections and presents its own certificate
- DNS queries resolve correctly (captive portal often allows DNS through) but TCP connections to any IP return the portal page
- The application appears to work after manually visiting http://captive.apple.com or similar detection URL in a browser and completing the portal login
Root Causes
- Wi-Fi network requires web-based authentication (click-through agreement or credentials) before granting internet access — until authenticated, the network intercepts and redirects all HTTP traffic
- Captive portal intercepting HTTP requests and returning a 511 response or a redirect to the login page instead of the intended API response
- DNS hijacking by the network — the portal intercepts DNS queries and returns its own IP for all domains, causing all HTTP requests to hit the portal server
- Application not implementing captive portal detection — it interprets the portal HTML as a server error rather than a network state issue
- VPN client failing to establish a tunnel because the captive portal blocks all traffic on VPN ports (UDP 500/4500 for IPSec, UDP 1194 for OpenVPN) before authentication
Diagnosis
**Step 1 — Detect captive portal state with a probe request**
```bash
# Apple's captive portal detection endpoint returns 'Success'
curl -s http://captive.apple.com/hotspot-detect.html
# If captive: Returns portal HTML (not 'Success')
# If open internet: Returns '<HTML><HEAD><TITLE>Success</TITLE>...</HTML>'
# Alternative: connectivity check endpoints
curl -s http://connectivitycheck.gstatic.com/generate_204
# 204 = open internet, anything else = captive or blocked
curl -s http://www.msftconnecttest.com/connecttest.txt
# 'Microsoft Connect Test' = open internet
```
**Step 2 — Check what the network returns for a known good URL**
```bash
# Use verbose to see redirect chain and response body
curl -v http://neverssl.com
# If captive: Response is portal HTML, possibly after a 302 redirect
# Inspect Location header to find the portal URL
```
**Step 3 — Attempt HTTPS to confirm TLS interception**
```bash
# If the portal intercepts HTTPS, you'll see a certificate error
curl -v https://api.example.com/health
# Captive portal: SSL certificate problem: certificate is not trusted
# (portal presents its own self-signed cert)
# Open internet: Valid certificate, expected API response
```
**Step 4 — Test VPN connectivity**
```bash
# Try Tailscale (uses DERP relay over HTTPS port 443 — often not blocked)
tailscale ping 100.x.x.x
# Or try WireGuard over port 443 (most portals allow port 443 outbound)
# If VPN connects through port 443, all subsequent traffic bypasses the portal
```
```bash
# Apple's captive portal detection endpoint returns 'Success'
curl -s http://captive.apple.com/hotspot-detect.html
# If captive: Returns portal HTML (not 'Success')
# If open internet: Returns '<HTML><HEAD><TITLE>Success</TITLE>...</HTML>'
# Alternative: connectivity check endpoints
curl -s http://connectivitycheck.gstatic.com/generate_204
# 204 = open internet, anything else = captive or blocked
curl -s http://www.msftconnecttest.com/connecttest.txt
# 'Microsoft Connect Test' = open internet
```
**Step 2 — Check what the network returns for a known good URL**
```bash
# Use verbose to see redirect chain and response body
curl -v http://neverssl.com
# If captive: Response is portal HTML, possibly after a 302 redirect
# Inspect Location header to find the portal URL
```
**Step 3 — Attempt HTTPS to confirm TLS interception**
```bash
# If the portal intercepts HTTPS, you'll see a certificate error
curl -v https://api.example.com/health
# Captive portal: SSL certificate problem: certificate is not trusted
# (portal presents its own self-signed cert)
# Open internet: Valid certificate, expected API response
```
**Step 4 — Test VPN connectivity**
```bash
# Try Tailscale (uses DERP relay over HTTPS port 443 — often not blocked)
tailscale ping 100.x.x.x
# Or try WireGuard over port 443 (most portals allow port 443 outbound)
# If VPN connects through port 443, all subsequent traffic bypasses the portal
```
Resolution
**Resolution 1: Programmatic captive portal detection**
```python
import httpx
CAPTIVE_PORTAL_CHECK_URL = 'http://captive.apple.com/hotspot-detect.html'
EXPECTED_CONTENT = 'Success'
def is_captive_portal() -> bool:
try:
resp = httpx.get(
CAPTIVE_PORTAL_CHECK_URL,
timeout=5.0,
follow_redirects=False, # A redirect means captive portal
)
if resp.status_code in (301, 302, 307, 511):
return True
return EXPECTED_CONTENT not in resp.text
except Exception:
return True # Cannot reach check URL → assume captive
if is_captive_portal():
raise RuntimeError(
'Captive portal detected. Open a browser and complete '
'the Wi-Fi authentication before running this script.'
)
```
**Resolution 2: Use Tailscale to bypass captive portals**
```bash
# Tailscale routes traffic through HTTPS port 443 (DERP relay)
# Most captive portals allow 443 after login, but Tailscale uses it
# even before — enabling connectivity without full portal auth
tailscale up
# Once connected, all traffic routes through Tailscale's encrypted tunnel
```
**Resolution 3: Build retry logic for automated scripts**
```python
import time
import httpx
def wait_for_internet(max_wait_seconds: int = 300) -> None:
start = time.time()
while time.time() - start < max_wait_seconds:
try:
resp = httpx.get('http://captive.apple.com/hotspot-detect.html', timeout=5)
if 'Success' in resp.text:
return
except Exception:
pass
print('Waiting for internet access...')
time.sleep(10)
raise TimeoutError('Internet access not available after 5 minutes')
```
```python
import httpx
CAPTIVE_PORTAL_CHECK_URL = 'http://captive.apple.com/hotspot-detect.html'
EXPECTED_CONTENT = 'Success'
def is_captive_portal() -> bool:
try:
resp = httpx.get(
CAPTIVE_PORTAL_CHECK_URL,
timeout=5.0,
follow_redirects=False, # A redirect means captive portal
)
if resp.status_code in (301, 302, 307, 511):
return True
return EXPECTED_CONTENT not in resp.text
except Exception:
return True # Cannot reach check URL → assume captive
if is_captive_portal():
raise RuntimeError(
'Captive portal detected. Open a browser and complete '
'the Wi-Fi authentication before running this script.'
)
```
**Resolution 2: Use Tailscale to bypass captive portals**
```bash
# Tailscale routes traffic through HTTPS port 443 (DERP relay)
# Most captive portals allow 443 after login, but Tailscale uses it
# even before — enabling connectivity without full portal auth
tailscale up
# Once connected, all traffic routes through Tailscale's encrypted tunnel
```
**Resolution 3: Build retry logic for automated scripts**
```python
import time
import httpx
def wait_for_internet(max_wait_seconds: int = 300) -> None:
start = time.time()
while time.time() - start < max_wait_seconds:
try:
resp = httpx.get('http://captive.apple.com/hotspot-detect.html', timeout=5)
if 'Success' in resp.text:
return
except Exception:
pass
print('Waiting for internet access...')
time.sleep(10)
raise TimeoutError('Internet access not available after 5 minutes')
```
Prevention
- **Use a VPN (Tailscale recommended) for any work on untrusted networks** — once the VPN tunnel is established, all traffic bypasses captive portal interception regardless of the network's restrictions
- **Add captive portal detection to startup scripts** that run in varied network environments — fail fast with a clear message instead of confusing the operator with unexpected API errors
- **Deploy on networks you control** for production workloads — captive portals are a sign that the underlying network is not suitable for reliable automated service operation
- **Test with a mobile hotspot fallback** for critical scripts that must run from conference or hotel venues where captive portals are common
- **Add captive portal detection to startup scripts** that run in varied network environments — fail fast with a clear message instead of confusing the operator with unexpected API errors
- **Deploy on networks you control** for production workloads — captive portals are a sign that the underlying network is not suitable for reliable automated service operation
- **Test with a mobile hotspot fallback** for critical scripts that must run from conference or hotel venues where captive portals are common