Setup
Split Tunneling and the DNS Trap
Split tunneling is not one feature. Two mechanisms ship under one label, they break in different ways, and most clients never say which you are configuring. The complaint that fills support threads — I excluded the app and it still acts as if I am somewhere else — traces to a third thing neither mechanism controls: name resolution.
Two mechanisms, one name
- Destination rules. Addresses, prefixes, and sometimes domains decide what enters the tunnel. This is ordinary routing: the kernel picks an interface from the packet's destination. WireGuard's
AllowedIPsis the clearest example, one list serving as both routes outbound and an access list inbound. - Application rules. A filter decides by process: sockets owned by listed programs go one way, everything else the other. On Windows that is usually a kernel driver hooked into the Windows Filtering Platform, on macOS a system extension, on Android part of the platform's own VPN API.
They answer different questions — where is this packet going versus who opened this socket. A routing table has no idea which program produced a packet; a process filter has no opinion about geography. Clients offering both keep them as separate rule sets with a precedence order.
Destination rules are exact; domain rules are not
A full tunnel is a default route through the tunnel interface; a split tunnel narrows that to specific prefixes, and longest-prefix match settles every packet. Two things still go wrong. A policy that routes 0.0.0.0/0 into the tunnel but says nothing about ::/0 leaves IPv6 on the local link, and a dual-stack host usually reaches for the IPv6 address first when a name has one. And a prefix says nothing about which program uses it, so an exclusion hits every process at once.
Domain rules look friendlier, but routing still needs an address, so the client watches DNS answers and routes whatever came back. That mapping is a snapshot, and a poor one on content delivery networks: one address there serves many unrelated domains, and the addresses returned for a name rotate on short TTLs. The rule is therefore approximate in both directions — it pulls in unrelated traffic sharing an address, and misses your domain when the newest answer points somewhere it has not learned.
The DNS trap
A tunnel usually adds a second resolver alongside the one the local network handed out. Which one a program uses is decided by the operating system's resolver-selection rules, which work per interface or per scope — not per split-tunnel rule. Hence a mismatch easy to state and hard to see: the answer is right for one path, and the connection takes the other.
- The excluded application resolves through the tunnel's resolver, gets an address selected for the exit's location, then connects to it from your own address. Symptoms: a distant endpoint, latency that makes no sense for a direct connection, or a service that finds resolver and source address inconsistent and declines.
- The tunneled application resolves locally, gets an address near you, and reaches it the long way around through the exit — with the local resolver having logged the name anyway.
Windows adds a structural twist. Most programs do not send DNS queries themselves; they call into dnsapi.dll, which hands the query to the DNS Client service hosted in svchost.exe. A per-application filter watching sockets sees the query coming from a system service rather than your app, and applies the default rule unless the client special-cases that service: the application's traffic is excluded, its name resolution is not.
Browsers add a layer. A browser doing DNS over HTTPS resolves inside its own processes, over an ordinary HTTPS connection, so it follows the rule applied to the browser and ignores the system's resolver settings. Firefox complicates this further: at its default protection level it turns DoH off when it detects a VPN or proxy on Windows, so the effective resolver changes with tunnel state.
The processes you did not list
Application rules apply to processes, not intentions. An app that hands a download to a system transfer service, a browser with a separate network process, an updater under its own name — none inherit the rule unless the filter follows children. A per-application rule covers the process you named and nothing you did not.
Android is the clean case: allow and deny lists belong to the platform's VPN API, enforced by the system rather than a third-party driver, and the unit is the app rather than one of its processes. A configuration may carry an allow list or a deny list, never both.
Inverse split tunneling
The default arrangement tunnels everything and lets you name exceptions. The inverse tunnels nothing and lets you name what goes in, usually labeled only allow selected apps to use the VPN. For the common goal — one application that must stay outside — the inverse is more predictable, because it removes the enumeration problem: none of that app's helpers are in the tunnel to begin with.
The trade: the two models fail in opposite directions. A misfire in the exception model tunnels traffic that should have gone direct: inconvenient, not exposing. A misfire in the inverse model sends traffic you meant to tunnel out directly.
What excluded traffic costs
Excluded applications use your real address. That is the point, and the entire cost: the destination sees where you are, the local network sees which names you looked up, and anything linking that session to a tunneled one — same account, same device — links them. Correlation does not care whether the payload was sensitive.
The kill switch often does not cover it either, and behavior differs by client: some block excluded applications when the tunnel drops, others let them keep running. Check yours — “kill switch on” and “nothing leaves unprotected” stop being the same statement once a split exists.
Worth doing, and not
- Reasonable: a banking app that treats unfamiliar networks as suspicious; printers and storage on the local segment, better served by a destination rule; bulk backups, where the tunnel only adds overhead.
- Poor: excluding the browser, normally the thing carrying your identity; excluding an app because it feels slow, before checking whether the exit, the MTU, or DNS is the bottleneck; long hand-maintained lists that drift.
When the result is strange
- 1Establish which mechanism you are using: process filter, destination or domain list, or both.
- 2Compare the DNS answer each side receives; different answers explain most of these reports.
- 3Check whether the application does DNS over HTTPS itself; if so, system resolver settings are not what it follows.
- 4Confirm both address families are handled — an IPv4-only policy on an IPv6-capable network is an unintended split tunnel.
Why does an excluded app still act as if my connection comes from another country?
Almost always because it still resolves names through the tunnel's resolver. It receives an address chosen for the exit's location, then connects to it directly, landing on a distant endpoint or on a service that sees resolver and source address disagree. Compare DNS answers inside and outside the tunnel.
Can I exclude a single app from the VPN on iPhone or iPad?
Not with a consumer VPN app. Per-application VPN on iOS is a managed-device feature: the mapping between an app and a VPN configuration comes from a device management profile, not from anything an app installs for itself. Destination-based rules are separate and available to a device-wide tunnel.
Does the kill switch protect apps I excluded?
Generally not, and it varies by client. Some block excluded apps when the tunnel drops, others let them keep running by design.
Is domain-based split tunneling reliable for large sites?
No. The client resolves the domain and routes the resulting addresses, but on content networks those addresses are shared with unrelated domains and rotate on short TTLs, so the rule catches traffic you did not mean to include and misses traffic you did.
Why doesn't my rule apply to the app's downloader or updater?
Because process filters match processes. If the application delegates work to a helper binary or a system service, that process has its own identity and gets the default rule unless the client follows children.
Did this help?
Nothing follows from the answer; only we see it.