Split Tunneling in VPN Apps: What iOS and Android Allow
Every split tunneling VPN app starts as one feature request: let users choose which apps skip the VPN. The platforms do not agree on what that means. Android supports per app rules in its VPN API, but showing users a list of installed apps runs into package visibility limits and a Google Play permission policy. On iOS, per app VPN is an MDM feature, so a consumer app splits traffic by destination route and DNS domain instead.
This guide gives the capability table, the Android app listing options and a test plan, drawn from Apple and Google documentation. It is for product leads and engineers scoping the feature.
Per App or Route Based: Which Split Do You Mean?
Per app split tunneling decides by which app sends the traffic: only these apps use the VPN, or every app except these. Route based split tunneling decides by destination: these IP ranges go through the tunnel, those do not. Users ask for the first because they think in apps. Operators often need the second, for example to keep a local network or a company range outside the tunnel.
Domain rules sit on top of routes. The network layer sees IP addresses, so “exclude this domain” means steering DNS or resolving names and acting on the answers. Each platform handles that differently. You can mix models, such as per app rules plus a local network exemption, but every extra rule is another test case.
How Does a Split Tunneling VPN App Work on Android?
Android’s VPN guide describes two list types on VpnService.Builder. addAllowedApplication() sends only the listed apps through the VPN. addDisallowedApplication() keeps the listed apps off it and sends everyone else through. You use one or the other: the platform source throws UnsupportedOperationException if you mix them, and both methods exist in the Android 5.0 (API 21) source. Build these details into your UI:
- Set lists before
establish(). Changing them later means building a new interface, so expect a short interruption and test the switchover. - An empty allowed list means all apps use the VPN. A user who clears an “only these apps” list silently gets a full tunnel.
- Apps must be installed when you add them. In disallowed mode, an app installed later goes through the VPN. In allowed mode, it stays outside. Say so in the UI.
- Disallowed apps use system networking as if the VPN were not running. That means the real IP and the underlying network’s DNS, by design.
For route based rules, addRoute() sends a destination range into the tunnel and the longest matching prefix wins. excludeRoute() and addRoute(IpPrefix) were added in Android 13 (API 33), per the reference. On older versions you exclude a range by adding routes for everything around it.
Two interactions catch teams out. With Block connections without VPN on, apps outside the tunnel lose their connection, per the guide, so warn users and detect it with isLockdownEnabled(). Our kill switch guide covers that setting. And if you never call addDnsServer(), Android uses the default network’s DNS servers for the VPN, per the same source, which is rarely what you intend. Our leak test plan has the checks.
How Can a Split Tunneling App List Installed Apps Without QUERY_ALL_PACKAGES?
This is the part that stalls releases, so it gets the most space.
What Changed in Android 11?
An app that targets Android 11 (API 30) or higher gets a filtered view of installed packages, and Google’s package visibility documentation says you declare what you need in a <queries> element. Google’s Android 11 notes say the visibility changes affect apps only when they target Android 11 or higher, but Google Play’s target API rules put a new release on the filtered side. See our target API guide.
What Does Google Play Allow?
QUERY_ALL_PACKAGES restores the full list. Android’s documentation says it is rarely needed and tells you to request the smallest visibility that works, and Google Play’s policy limits it to apps whose core user facing functionality requires broad visibility. Its examples are device search, antivirus, file managers and browsers. You must justify why a less intrusive method will not work, declare the permission in Play Console, and keep the data to the core purpose. Misuse can lead to removal.
The policy page does not mention VPN apps, per app VPN or split tunneling. A picker might be argued as core functionality, but nothing in the text promises approval. Plan on not getting it.
What Are the Alternatives?
The documented options are <package> entries for apps you know by name, <intent> entries that match an intent filter signature, and <provider> entries. An <intent> entry must contain exactly one action.
The open source WireGuard Android app shows a working pattern. Its manifest does not request QUERY_ALL_PACKAGES. It declares a <queries> intent for the main action with the launcher category, and its app picker lists packages that hold the INTERNET permission. A sound starting point:
- Declare the launcher
<queries>intent for the picker. - Add
<package>entries for apps you recommend by name, such as a curated exclusion list. - Filter the picker to apps that hold INTERNET.
- Explain the choice in your Play declaration.
Three caveats. Android’s documentation does not describe the launcher pattern, so treat it as observed practice and test it on Android 11 and later devices. Expect apps without a launcher activity to be missing from the list. And the Builder checks that a package is installed, while package visibility also filters getPackageInfo(), the call the guide’s sample uses. The pages we read do not say whether the Builder accepts an installed package your app cannot see, so test manual entry before you offer it.
What Does iOS Allow for Split Tunneling?
Not per app, for a consumer app. Apple’s NETunnelProviderManager documentation says the only way to configure per app VPN is to enroll the device in MDM and link managed apps to a VPN configuration. Apple system apps cannot get app rules, with Safari limited to certain domains. appRules and forPerAppVPN() are macOS only (10.15.4 and later). On iOS you can read rules with copyAppRules() but not create them. The NETestAppMapping key allows testing without MDM, only in builds signed with a development profile.
What a consumer iOS app can do is route by destination. Apple’s routing guide has you put routes in includedRoutes for traffic to tunnel and excludedRoutes for traffic to send outside. The default route, 0.0.0.0/0 or ::/0, catches whatever has no more specific system rule. Two caveats from that page: the system routing table supersedes both lists, so local network routes win, and an app that scopes a connection to an interface supersedes the table too.
enforceRoutes (iOS 14.2 and later) reverses that, scoping included routes to the VPN and excluded routes to the primary interface. Apple says it is mutually exclusive with includeAllNetworks, which wins. Apple does not say how excludedRoutes behave under includeAllNetworks, so do not assume split tunneling and kill switch behavior come as a pair.
Domains need care. NEDNSSettings.matchDomains creates split DNS: only hosts in the listed domains use the tunnel’s resolver. An empty string makes it the default domain, which Apple describes as how a split tunnel sends all DNS to the VPN servers first. If the tunnel is the default route, the list is ignored. That steers DNS, not traffic. Apple’s tech note TN3120 says to claim traffic for a packet tunnel by destination IP, or with per app VPN, and not to claim some traffic and proxy the rest elsewhere. In our reading, domain based traffic rules in a consumer app would have to be built on IP routes. We found no Apple page that documents that approach. Our guide to personal VPN versus packet tunnel provider covers the extension side.
What Can Each Platform Do?
| Capability | Android | iOS | macOS |
|---|---|---|---|
| Per app rules in a consumer app | Yes, allowed or disallowed list, not both | No, per app VPN needs MDM | APIs exist, described for MDM managed apps |
| Route based include | addRoute |
includedRoutes |
includedRoutes |
| Route based exclude | excludeRoute on Android 13 and later |
excludedRoutes |
excludedRoutes |
| Domain based rules | None built in | matchDomains steers DNS only |
Same, plus domain lists for managed per app VPN |
| Listing apps for a picker | Package visibility and Play policy apply | Not applicable | Not applicable |
| Local network exemption | Exclude the LAN range | excludeLocalNetworks, with includeAllNetworks or enforceRoutes |
Same |
On Windows, Microsoft’s routing documentation describes split tunneling for its built in VPN platform through route lists with exclusion routes. A custom tunnel app on Windows controls routes through its own adapter. Microsoft documents app based Traffic Filter rules for its VPN platform, but the pages we read describe no per app routing API for a custom adapter. On Linux, the wg-quick manual lets you choose the routing table, and the WireGuard network namespace notes describe launching selected programs inside the physical namespace as exceptions. TN3120 recommends a transparent proxy provider on macOS for selective claiming, and Apple lists that class from macOS 11.
What UX and Support Problems Follow?
- Excluded apps show the real IP and ISP. That is the feature. Say it where the user makes the choice, and in your listing.
- DNS differs by group. Included apps use the resolver you set. Excluded apps use the system’s. Check both.
- Local network access. Without an exemption, printers and casting break and users blame split tunneling. iOS defaults
excludeLocalNetworksto true, but it only applies withincludeAllNetworksorenforceRoutes. - Lockdown. Excluded apps go offline when Android’s block setting is on. Warn before it happens.
How Do You Test Split Tunneling?
Capture at a gateway you control, as in the leak test plan above. The pass conditions are targets for your own requirement, not documented platform behavior.
- Included app. Its traffic reaches only the VPN endpoint on the physical interface, for IPv4 and IPv6.
- Excluded app. It reaches its own servers directly. That is the pass condition.
- Change the list while connected. On Android, no included app’s traffic leaves during the switchover.
- Empty and full lists. The UI blocks or warns on an empty “only these” list.
- Install and uninstall. A listed app that disappears does not break the rebuild. A new app lands where the UI says.
- DNS. Included lookups reach your resolver, excluded lookups the system resolver, with no crossover.
- Local network. The printer and casting work as the UI promises.
- Lockdown and
includeAllNetworks. Behavior matches your documented rule, and the warning appears. - Package visibility. On Android 11 and later, the picker shows the apps you expect, and the missing ones match your launcher assumption.
FAQ
Can an iOS app offer per app split tunneling?
Not in a consumer app. Apple documents per app VPN as an MDM feature, and iOS apps can read app rules but not create them. Route based and DNS based splits are available.
Can I combine allowed and disallowed lists on Android?
No. Pick one list type per connection. The platform source throws UnsupportedOperationException when you mix them.
Do I need QUERY_ALL_PACKAGES for a split tunneling picker?
Not necessarily. Declare a <queries> launcher intent, as the open source WireGuard Android app does, and test the result. The Play policy page does not list VPN apps as an approved use, so do not rely on approval.
Does split tunneling leak the real IP?
For excluded apps, yes, by design. For included apps, a leak means a configuration error, such as a missing DNS server or an uncovered IPv6 path.
Does excludeRoute work on older Android versions?
No. It appears from Android 13 (API 33). Before that, add routes that cover everything except the range you want out.
The Takeaway
Decide which split you can offer on each platform before you design the screen: per app on Android, routes and DNS on iOS. Then budget for the Android app listing and its Play declaration, because it is the step most likely to slow a release.
Planning split tunneling for your VPN app? DigitalD.tech can help you scope what each platform allows, the app picker and a test plan before development starts. Tell us your platforms on the contact page.
Last checked against Google’s Android VPN guide, package visibility documentation, the Android platform source and Google Play’s QUERY_ALL_PACKAGES and target API pages, Apple’s Network Extension documentation and TN3120, Microsoft Learn’s VPN routing page, and WireGuard’s documentation and Android manifest on 30 October 2026.