Two requirements that sound equivalent are not: "exclude this app from the VPN" and "send this one destination outside the VPN for the laptop on my hotspot". The first changes how a socket on the phone selects a network. The second has to change a forwarding decision for a packet that arrived from another device after the phone already accepted it. Confusing the two is how a hotspot client ends up with an exception that only ever applied to the phone's own browser.

Two subsystems own the two questions

Android keeps app networking and tethering apart. App sockets are assigned to a network by the connectivity stack, and a VPN app decides which of its own sockets enter the tunnel. Forwarded packets from a tethered client are handled by the tethering subsystem, which asks netd for routes, policy rules and translation on the downstream interface.

A per-app exclusion is evaluated when an app opens a socket. A forwarded packet never reaches that check, because it did not originate from an app socket on this device. That is the whole reason the two settings fail to substitute for each other, and it is not a bug in either one.

What per-app exclusion actually changes

A VpnService builder can name packages that stay outside the tunnel with addDisallowedApplication, or name the only packages allowed inside it with addAllowedApplication. Those apps then open sockets on the underlying network while the VPN continues to own the default route for everything else.

The scope is precise: the package's own sockets, on this device, for as long as the tunnel is up. It says nothing about forwarded traffic, nothing about a second device, and nothing about packets the system sends on behalf of tethering.

It is not a destination control either. Excluding a browser excludes every address that browser reaches, including the one you wanted to keep inside the tunnel. When the requirement names a destination rather than an app, the app selector is the wrong tool by construction.

What a tethered packet looks like to the phone

The client's packet arrives on the hotspot interface with a private source address assigned by the phone, say 192.168.43.20, and a public destination. To forward it the phone needs a route toward an uplink and a translation rule so replies can find their way back, because the client's private address is meaningless on the cellular network.

Android expresses that with policy rules that select a routing table, and netd rebuilds the relevant tables and rules when networks and VPNs change. A table number, rule priority or interface name copied from a forum post, or from the same model on an older release, does not establish durable ownership. That rebuild is also why a hand-written rule can work for an hour and stop after the next reconnect.

The policy a hotspot exception actually needs

Write the decision as source, destination and intended path before touching a rule. A minimal shape for one approved destination looks like this:

SourceDestinationIntended path
One authorized hotspot clientOne approved public service addressOrdinary cellular uplink
The same clientAll other covered destinationsShared VPN
The phone's own appsTheir existing destinationsUnchanged phone policy

Both directions have to be complete. The direct branch needs a usable route through the cellular uplink that happens to be current; the VPN branch needs the tunnel route plus permission to forward. Any source translation must match the egress you chose, because NAT applied at a tunnel interface presents the tunnel address and not the cellular one, and the return path then disagrees with the outbound path.

Replies must reach the same downstream client through tracked state or an explicit route, and an outbound packet counter proves none of that. Keep the VPN's own outer transport outside the exception's scope as well, or the tunnel's carrier packets inherit a policy written for a client's web request.

One captured address is not a destination

A single IPv4 address read from a browser is a snapshot. Large services answer from anycast or a rotating set of edge addresses, many are dual-stack, and a login flow can touch authentication and telemetry hosts that the page never displays. An exception built around the address you happened to see breaks the next time the service rebalances.

Use the service's owner's documented addresses, or a range it publishes, and account for both address families. If the requirement cannot be expressed as a stable set, it is probably a policy question for the service rather than a routing trick on a phone.

Verify from the downstream side

Three checks, in this order: the selected service from the laptop, an ordinary destination that must stay inside the VPN, and a phone app that must be unaffected. Then repeat after a reconnect and after an uplink change, because that is when the policy tables are rebuilt.

If the phone's sharing implementation cannot hold that contract across a reconnect, move the selective policy to a supported downstream router rather than accumulating root rules on the handset. A router can express source and destination matching with a stable owner, and the tethering module documentation is the right reference for what the phone is actually doing.

Ũhoro ũhĩtĩte