Captive Portals - DNSCrypt/dnscrypt-proxy GitHub Wiki

Captive Portals

Introduction

Operating systems perform connectivity checks after network changes to determine if internet access is available or if a captive portal (like a hotel or airport Wi-Fi login page) is in the way.

These checks involve resolving specific domain names and fetching content from known URLs. If these checks fail, the OS may report "No Internet" or "Limited connectivity" even when the network is working fine.

When using dnscrypt-proxy, these checks can fail during startup because:

  1. The proxy hasn't fully initialized yet
  2. The upstream DNS servers haven't been contacted yet
  3. DNS rebinding protection might block the responses

The [captive_portals] feature solves this by providing instant, hard-coded responses for these connectivity check domains.

How it works

When enabled, dnscrypt-proxy:

  1. Starts a listener immediately - Before the proxy fully initializes, a lightweight UDP listener starts handling captive portal queries. This "cold start" mode ensures connectivity checks pass even during startup.

  2. Returns instant responses - Queries for domains in the map file get immediate responses without contacting upstream servers.

  3. Uses short TTLs - Responses have a TTL of 1 second, so they won't be cached for long if the actual IP addresses change.

  4. Supports both IPv4 and IPv6 - A records and AAAA records are both handled based on the IPs in your map file.

Configuration

Enable the feature by uncommenting the map_file line in the [captive_portals] section of your dnscrypt-proxy.toml:

[captive_portals]

map_file = 'captive-portals.txt'

The path can be absolute or relative to the configuration file location. An example file example-captive-portals.txt is included with dnscrypt-proxy.

File format

The map file is a simple text file with one entry per line:

hostname    IP1, IP2, IP3

Rules:

  • Hostname and IPs are separated by whitespace (spaces or tabs)
  • Multiple IPs are separated by commas
  • IPv6 addresses don't need brackets (no port numbers involved)
  • Lines starting with # are comments
  • Wildcards are not supported - use exact hostnames only

Example:

# Apple connectivity check
captive.apple.com               17.253.109.201, 17.253.113.202

# Google/Android connectivity check
connectivitycheck.gstatic.com   64.233.162.94, 74.125.132.94

# Windows NCSI
www.msftncsi.com                13.107.4.52
dns.msftncsi.com                131.107.255.255, fd3e:4f5a:5b81::1

Platform-specific entries

Apple (macOS, iOS)

captive.apple.com               17.253.109.201, 17.253.113.202

Android

connectivitycheck.gstatic.com   64.233.162.94, 64.233.164.94, 64.233.165.94
connectivitycheck.android.com   64.233.162.100, 64.233.162.101, 64.233.162.102

Windows

www.msftncsi.com                2.16.106.89, 2.16.106.91, 23.0.175.137
dns.msftncsi.com                131.107.255.255, fd3e:4f5a:5b81::1
www.msftconnecttest.com         13.107.4.52
ipv6.msftconnecttest.com        2a01:111:2003::52

For additional Windows-specific configuration, see Network Connectivity Status Indicator (NCSI).

DNS64

The ipv4only.arpa domain is used by devices to detect NAT64/DNS64 networks:

ipv4only.arpa                   192.0.0.170, 192.0.0.171

NTP servers

Adding NTP server entries can help with time synchronization during early boot, before the proxy is fully operational:

time.google.com                 216.239.35.0, 2001:4860:4806::
time.apple.com                  17.253.34.123
time.windows.com                168.61.215.74

Interaction with DNS rebinding protection

If you're using DNS rebinding protection and a captive portal domain resolves to a private IP address, you can either:

  1. Add the domain to your allowed-names.txt file
  2. Add the domain to your captive portals map file with its expected IP

The captive portals feature takes precedence and returns responses before rebinding protection is applied.