Always-on and kill switch sit two lines apart in the same settings screen, so people read them as two names for one feature. They are not. Always-on is a reconnect policy: start the tunnel at boot, bring it back after a crash or a network change, and keep retrying while it fails. A kill switch is a traffic policy: it names the packets that may still leave the machine while the tunnel is down. You can enable the first without the second, and on most platforms the stock result still lets traffic out during the gap.

Check 1: is the block a switch of its own?

On Android 10 and later, a saved VPN profile opens to two independent toggles behind the gear icon: Always-on VPN and Block connections without VPN. Only the second one is the kill switch. With always-on alone, the tunnel does come back after a drop, and any application mid-request can reach the network over Wi-Fi or mobile data until it does.

Managed devices keep the same split in the policy API. setAlwaysOnVpnPackage() takes the package name and a lockdownEnabled flag; an administrator who sets the package and leaves the flag false has built a persistent tunnel, not a closed device. Console wording rarely makes that difference visible.

Read the state from the device instead of the console: adb shell settings get global always_on_vpn_app and adb shell settings get global always_on_vpn_lockdown. If the first returns null, no package is enforced at platform level; if the second returns 0, the platform is not blocking anything. The Android VPN guide documents these as separate capabilities rather than one mode.

Check 2: which layer owns the block?

Labels live in the client and blocks usually live in the firewall, which is why the same word means different things on each platform. The rows below are the ones worth confirming before you trust any of them.

Where you see the labelWhat the label promisesWhat actually blocks
Android VPN profileAlways-on VPNBlock connections without VPN
Android device policyAlways-on packagelockdownEnabled flag
Windows built-in VPNAlways On tunnelOutbound firewall rules
WireGuard for WindowsTunnel serviceBlock untunneled traffic option
wg-quick on LinuxRoute via AllowedIPsYour own nftables or iptables rules

Windows is the clearest example of a label that does not block. Always On there describes tunnel persistence: a device tunnel for machine authentication, a user tunnel for the signed-in account, configured through profile XML or Add-VpnConnection. Nothing in that profile stops an outbound packet when the tunnel is missing, and outbound filtering is not on by default, so a laptop on Wi-Fi keeps working while the VPN is down.

WireGuard splits the same way, but by client. WireGuard for Windows carries a per-tunnel Block untunneled traffic option that installs filter rules; the Linux tooling has no equivalent switch. wg-quick adds a route for each AllowedIPs entry plus an fwmark rule, and wg-quick down removes both, so the physical default route takes over again with no packet dropped in between.

On Apple platforms, always-on arrives as a management-profile feature for supervised devices and is expressed as on-demand rules rather than a checkbox. The question to answer from the payload is whether a failed tunnel denies traffic or merely requests another connection. The menu title will not tell you.

Check 3: measure the gap, not the icon

Four transitions matter, and they fail differently: boot before anyone logs in, sleep and resume, a Wi-Fi to mobile-data change, and a client crash that leaves the tunnel interface half-configured. A clean reconnect on one of them proves nothing about the other three.

The cheapest test stops the tunnel on purpose and then tries to leave. With the tunnel down, curl -m 5 -sS -o /dev/null -w '%{http_code}' https://example.com returning 200 means the policy is fail-open; a timeout or a refused connection means something is still blocking. ip route get 1.1.1.1 names the interface that would carry the packet, which is usually more informative than the tray icon, and systemctl status wg-quick@wg0 shows whether the interface was cleanly removed.

Run the same probe in a second window while the network is changing, not only after it settles. The interval between the drop and the retry is the part that matters, and a name lookup is the fastest way to notice that traffic escaped: if the local resolver answers while the tunnel is down, the block is not complete.

Check 4: decide the exceptions before you lock down

Two exceptions are structural. The client has to reach the VPN server itself, so a rule set that drops everything also prevents the reconnect that would restore protection. A captive portal cannot be reached through a tunnel that is not up either, so a locked device on hotel Wi-Fi needs a documented way to open the portal rather than an improvised one.

A minimal shape, endpoint allowed and everything else dropped:

table inet vpnlock {
  chain output {
    type filter hook output priority 0; policy drop;
    oifname "lo" accept
    ip daddr 203.0.113.10 udp dport 51820 accept
    oifname "wg0" accept
  }
}

Treat that endpoint line as a variable rather than a constant. Providers rotate addresses, and a stale exception is the usual reason a working kill switch turns into a device that cannot reconnect. Local printers, NAS shares and development services belong in the same written list, or they get discovered one support ticket at a time.

Check 5: record what you actually proved

Keep a four-column table and fill it by testing: state, expected behaviour, observed behaviour, evidence. Rows are the transitions from check 3 plus one deliberate failure. A row you cannot fill with command output is a row you have not tested, and a device that passes the reconnect row while failing the reboot row is common enough to be worth the extra ten minutes.

Before you call it done:

  • Confirm the blocking setting is separate from the reconnect setting, and read its value on the device.
  • Know which layer enforces the block - client option, device policy or firewall - and where that layer is configured.
  • Keep one probe command that proves the block, and run it during a transition rather than after.
  • Write the exception list down, endpoint included, and note who refreshes it.
  • On managed hardware, leave the enforced policy alone; removing the block changes the requirement instead of fixing the tunnel.

Аффингæй хæстæг статьялтæ