VPN Kill Switch: How It Works on iOS, Android and Desktop
Adding a kill switch toggle to a VPN app is the easy part. Making it hold when the tunnel drops, the app crashes or the phone moves from Wi Fi to cellular is the real work.
A VPN kill switch blocks traffic that would otherwise leave outside the tunnel while the VPN is disconnected or reconnecting. Whether it holds depends on who enforces it: the operating system or your app. On iOS and Android the system level options are flags and settings with documented limits. On Windows and Linux you build the block yourself with the firewall. This guide covers each platform from primary documentation and ends with a test procedure you can hand to QA.
What Does a VPN Kill Switch Have to Block?
Two moments matter. The first is the gap after the tunnel drops. The second is the reconnect itself, when apps are already retrying. A kill switch must block ordinary traffic in both, while still letting the VPN client reach its own server.
There are two ways to do that. An app level kill switch is code in your client: it notices the drop, then blocks or discards packets. A system enforced kill switch is a rule the operating system keeps in force whether or not your app is alive. The difference shows in the worst case. If your process dies, an app level kill switch dies with it. A system rule does not.
So write the requirement per platform, as “what the platform enforces, plus what our app adds”.
How Does a VPN Kill Switch Work on iOS?
The system enforced option on iOS is includeAllNetworks, a property of NEVPNProtocol available from iOS 14 and macOS 10.15. It works for personal VPN and packet tunnel providers. Apple’s routing guide says that when the VPN transitions away from the connected state, the system drops network traffic. Its examples are a move from Wi Fi to cellular and the reconnect over cellular. When the VPN goes from disconnected to connecting, existing TCP connections stay open but their later packets are dropped.
Which Properties Adjust the Exceptions?
Four related properties change what stays outside the tunnel:
| Property | What Apple documents | Available from |
|---|---|---|
excludeLocalNetworks |
Excludes local hosts such as AirPlay, AirDrop and CarPlay, only when includeAllNetworks or enforceRoutes is also on. Default false on macOS, true on iOS. |
iOS 14.2, macOS 10.15 |
excludeAPNs |
Excludes push notification traffic, only with includeAllNetworks. Default true. |
iOS 16.4, macOS 13.3 |
excludeCellularServices |
Excludes Wi Fi Calling, MMS, SMS and Visual Voicemail, only with includeAllNetworks. Default true. |
iOS 16.4, macOS 13.3 |
enforceRoutes |
Makes your included and excluded routes override the routing table and app scoping. Ignored when includeAllNetworks is on. |
iOS 14.2, macOS 11 |
Read the defaults twice. With includeAllNetworks on, push and cellular services traffic leave the tunnel unless you turn those two properties off, and Apple warns that routing push traffic through a VPN can mean missed or delayed notifications. Also, enforceRoutes is a routing rule. Apple says nothing about blocking when the tunnel is down, so it is not a kill switch on its own.
Some traffic is always outside the tunnel, whatever your app sets: DHCP and other control plane traffic, captive portal negotiation, cellular only services such as VoLTE, and companion device traffic such as an Apple Watch.
What Helps, and What Is Not Possible?
The tunnel provider should set reasserting to true whenever it starts to reconnect. On demand rules (off by default) can start or stop the VPN, but Apple describes them as rules that trigger the VPN, and the pages we read give them no blocking role. Pairing on demand to bring the tunnel back with includeAllNetworks to block meanwhile is our suggested design, so test it. Our guide to personal VPN versus packet tunnel provider covers the choice that comes first.
What is not possible: an always on VPN that holds regardless of your app needs an always on profile on an MDM supervised device, per the same Apple guide. The pages we read describe no API that stops a user turning the VPN off, or that blocks traffic when no VPN configuration is active.
How Does a VPN Kill Switch Work on Android?
Android’s system enforcement is two settings, not an API your app can call. Always on VPN, available from Android 7.0 (API 24), lets the system start your VpnService at boot and manage its lifecycle. The second, Block connections without VPN, makes the system block any traffic that does not use the VPN. Google’s VPN guide says the second can be switched on by the person using the device or an IT admin.
What Can an Android App Do?
Detect, guide and adapt:
- Detect. Android 10 added
isAlwaysOn()andisLockdownEnabled(), per Google’s Android 10 enterprise notes. Call them after connecting to the service and establishing the local interface. Always on status stays fixed while the service runs, but lockdown status can change, so check again on resume. Below Android 10, the VPN guide suggests tagging your own start intents and treating an untagged start inonStartCommand()as a system start. - Guide. The platform source defines
Settings.ACTION_VPN_SETTINGSand warns that a matching activity may not exist. Wrap the call in a try block and show written steps as a fallback, since menu names differ by device. - Adapt. A system start has no UI, so save your configuration between starts. Disable any control that disconnects when always on owns the lifecycle.
It cannot switch the settings on. DevicePolicyManager.setAlwaysOnVpnPackage() exists, but Google describes it for device owners and profile owners, meaning managed devices. A consumer app can only ask.
Two traps follow. First, lockdown and split tunneling collide: the guide cautions that when non VPN traffic is blocked, apps left out of the VPN by your allowed or disallowed list “lose their network connection”. See our split tunneling guide. Second, without the settings, only your code covers the gap. By the time the system calls onRevoke(), another network interface is already routing traffic. One app level design keeps the tunnel interface up while the gateway connection is rebuilt, dropping packets meanwhile. That is a design choice, not documented platform behavior, so test it on real devices.
What About Windows, macOS and Linux?
Windows. A kill switch here is a set of Windows Filtering Platform filters that permit the tunnel, the VPN endpoint and loopback, and block the rest. Lifetime is the catch. Microsoft’s object management page says objects added in a dynamic session are deleted when the session ends, including when the client process terminates, while persistent objects live until deleted. Filters added in a dynamic session vanish when your process dies. Persistent ones survive, but need a clean removal path, or users stay offline.
WireGuard for Windows shows the pattern. Its network notes describe a firewall that engages when a single peer has a /0 allowed IP. It permits its own service, DNS to configured servers, loopback, tunnel traffic, DHCP and NDP, and blocks the rest. Windows’ own VPN platform has a LockDown profile that blocks outbound traffic when the VPN is unavailable, but Microsoft limits it to the IKEv2 connection type for built in VPN. The page we read does not say whether a custom protocol client can use it, so check before you plan around it.
macOS. It uses the same NetworkExtension keys as iOS, with the availability shown above.
Linux. The nftables output hook sees packets from local processes, and a base chain defaults to accept unless you set the policy, per the nftables wiki. A minimal shape, to adapt and test on your distribution:
table inet vpn_killswitch {
chain out {
type filter hook output priority 0; policy drop;
oifname "lo" accept
oifname "wg0" accept
ip daddr 203.0.113.10 udp dport 51820 accept
ip6 daddr 2001:db8::10 udp dport 51820 accept
icmpv6 type { nd-router-solicit, nd-neighbor-solicit, nd-neighbor-advert } accept
}
}
WireGuard’s wg-quick manual documents the same idea with iptables, inserted on PostUp and removed on PreDown. It adds that most DHCP traffic still passes, because DHCP clients often bypass Netfilter.
Where Does a Kill Switch Leak or Hold?
| Failure | iOS with includeAllNetworks |
Android | Windows and Linux rules |
|---|---|---|---|
| Reconnect gap | Dropped while not connected, per Apple | Only your code covers it, unless both system settings are on | Blocked while rules stay, endpoint excepted |
| Network change | Dropped during the switch and reconnect, per Apple | Same as above | Allow the endpoint on any interface, not the old one |
| App crash | Not documented for the extension, so test | App level block dies. With both settings on, the platform source says the system restarts the service and apps cannot bypass the VPN, so confirm it on your devices | Windows dynamic filters are deleted with the process, per Microsoft. Persistent filters and Linux rules should stay, so confirm with the test below |
| Process killed | Not documented, so test | As a crash. Lockdown is documented to block meanwhile, so confirm it | As a crash |
| IPv6 | Configure ipv6Settings and ::/0. Apple does not say what happens if you do not |
A family with no address, route or DNS is blocked, but allowFamily lets it fall through to the underlying network |
Needs ip6 rules. An inet table covers both |
| Local network | excludeLocalNetworks exempts it |
Not documented in the pages we read, so test printers and casting | Your rules decide. WireGuard’s examples do not exempt the LAN |
The Android IPv6 cell follows the allowFamily notes in the platform source.
What Can an App Not Promise?
An app cannot override the exceptions iOS always applies. It cannot turn on Android’s settings, stop the user switching them off, or control what an OEM adds. It cannot cover the time before the first connection, or any time no VPN configuration is active. On Windows and Linux, an administrator or another tool can remove your rules.
What an app can promise is narrower and testable: “when the tunnel drops under these conditions, on these platforms, traffic outside it is blocked.” Marketing claims about leak protection can carry legal exposure. This is not legal advice, so have counsel review the wording before it reaches a store listing.
How Do You Test a VPN Kill Switch?
Capture at a gateway you control, such as a laptop hotspot, and filter out your VPN endpoint. Our leak test plan has the commands. Anything else the device sends that is not a documented exception is a failure. The pass conditions below are targets for your own requirement, not documented platform behavior, and the earlier cells that say “test” are open questions to answer on real devices.
| Platform | Run | Pass if |
|---|---|---|
| iOS | Block the endpoint at the gateway, then switch Wi Fi to cellular, toggle airplane mode and relaunch the app. Apple’s rvictl trace shows the whole device stack, with the interface in packet metadata |
Only documented exceptions leave a physical interface while status is not connected |
| Android | Run three configurations: no settings, Always on VPN only, and both settings. In each, force stop the app, reboot and toggle airplane mode | With both settings, nothing leaves outside the tunnel, and any exception you see is recorded. Without them, the leak window is measured and disclosed |
| Windows | End the client in Task Manager, stop its service, sleep and resume, move from Wi Fi to Ethernet | Nothing leaves but the endpoint and the exceptions you chose to allow, such as DHCP and NDP. After a clean exit or uninstall, the rules are gone |
| macOS | As iOS, but move from Wi Fi to Ethernet in place of cellular, and turn Wi Fi off and on in place of airplane mode. Capture with tcpdump on the active interface |
As iOS |
| Linux | kill -9 the client, then list the ruleset and ping |
The rules remain and traffic is blocked. A deliberate disconnect removes them |
FAQ
Does a VPN kill switch work if the app crashes?
Only if the block is enforced outside the app’s process: both system settings on Android, persistent filters on Windows, kernel rules on Linux. Those are documented designs, not results we measured, so run the crash test on each. On iOS, test it, because the pages we read do not document extension crashes.
What does includeAllNetworks do on iOS?
It tells the system to send most traffic through the tunnel and drop it whenever the VPN leaves the connected state. Some traffic is always excepted, and push and cellular services are excepted by default.
Can an Android app turn on Block connections without VPN?
No. The user or an IT admin turns it on. Your app can detect it with isLockdownEnabled() on Android 10 and later, and send the user to settings.
Is Always on VPN the same as a kill switch?
No. Always on VPN keeps the system starting your service. Traffic outside the VPN is blocked only when the second setting is also on.
Does a kill switch block local network access?
It can. iOS has excludeLocalNetworks, but WireGuard’s Windows and Linux examples have no LAN exception. Decide on purpose, then test printers and casting.
The Takeaway
Treat a VPN kill switch as a per platform requirement with a test attached, not a toggle. Write down what the platform enforces, what your app adds, and which cases you will disclose as limits.
Want us to review your kill switch requirement before development? DigitalD.tech can help you plan platform by platform behavior and a test plan to go with it, and our VPN QA and test automation service covers leak and kill switch testing.
Last checked against Apple’s Network Extension and packet trace documentation, Google’s Android VPN guide and Android enterprise notes, the Android platform source, Microsoft Learn’s Windows Filtering Platform and VPN pages, WireGuard’s documentation and the nftables wiki on 10 November 2026.