When the emulator fails to reach a build server that the host browser opens fine, the useful question is not whether the VPN works. It is which of two mechanisms the guest is missing. The virtual device runs its own network stack behind the emulator NAT, and its resolver list is a copy taken at boot, so a tunnel that came up afterwards changes the host path without changing what the guest asks. Understanding those two layers explains almost every variant of this problem.

The guest is a separate stack behind a fixed NAT

QEMU user-mode networking puts the guest at 10.0.2.15 with a gateway at 10.0.2.2, and the DNS service at 10.0.2.3 through 10.0.2.6. Outbound guest traffic is not forwarded by the host kernel; it is translated into host-side sockets, one per guest connection, and those sockets leave from the host address space. That is why a guest connection inherits the host routing decision at the moment it is created, and why the guest has no route table entry that names your tunnel.

The same mechanism gives the guest a useful escape hatch. 10.0.2.2 is an alias for the host loopback interface, so a service listening on 127.0.0.1 on the laptop is reachable from the guest at that address. Anything the client exposes only on loopback can be reached that way, which is the basis for the proxy arrangement described further down.

What the guest cannot do is observe the host network as if it were a namespace on it. There is no bridge, no shared route table and no inherited resolver, only the NAT service that accepts its packets and the snapshot the launcher took when the virtual device started.

DNS is a snapshot, not a live view

At startup the emulator reads the host resolver list and hands the guest up to four servers, exposed at the 10.0.2.3 to 10.0.2.6 addresses. If the tunnel was down at that moment, the snapshot contains whatever public resolver the laptop used, and the guest keeps asking it for the rest of its life. Public names resolve, internal names fail, and every symptom points at the VPN when the actual stale value is a resolver list captured minutes before the tunnel came up.

The ordering fix is therefore not folklore. Connect the tunnel first, then restart the emulator, and prefer a cold boot over a snapshot restore, because a quick-boot image can return with the resolver and route state it saved earlier. If you need to see what the guest currently uses, ask the device rather than the host:

adb shell dumpsys connectivity | grep -i -A2 dns
adb shell ping -c 1 build.internal.example

The emulator also accepts an explicit resolver at launch, which is useful when you know exactly which server the guest should use and dangerous when that server is public:

emulator -avd Pixel_API_34 -dns-server 203.0.113.53

A public resolver has no copy of an internal zone, so pinning one turns an ordering problem into a permanent failure. If the profile pushes private DNS, let the tunnel supply it and cold boot the guest instead of hard-coding an address.

Routing follows the host at connection time

Because guest connections become host sockets, the routing decision is made when the socket is created. Tunnel up before the request means the request leaves through the tunnel, subject to the profile routes. A split-tunnel profile that carries only selected prefixes will happily leave your private subnet outside, so host reachability is a hypothesis about the guest, not a result. Existing guest connections keep the path they were created with, which is why a client that reconnects does not always fix a stuck download inside the emulator.

Verification belongs on the host interface, not in the guest statistics:

sudo tcpdump -ni tun0 'host 203.0.113.10'

If the guest request appears on the tunnel interface, routing is working and the problem is above it. If it appears on the physical interface, the profile does not cover that prefix, and no amount of guest-side reconfiguration will change it.

When the client only offers a loopback proxy

Some commercial clients do not create a system tunnel at all. They expose an HTTP or SOCKS listener on 127.0.0.1 and expect applications to use it. The guest cannot see that socket directly, but the emulator can be told to use it, either through the extended controls (Settings, then Proxy, manual mode, host 127.0.0.1 and the port printed by the client) or at launch:

emulator -avd Pixel_API_34 -http-proxy http://127.0.0.1:8080

Copy the port from the client window; a guessed port fails silently and looks like a broken tunnel. Keep in mind what this covers: an HTTP proxy setting is honored by proxy-aware applications, and raw sockets ignore it entirely. If the workload is a native binary that opens its own connections, the proxy path is not equivalent to a tunnel and the comparison has to be made explicitly.

Prove the result from inside the guest

Separate resolution from routing with two tests in the same session. A name that fails while the numeric address answers is a DNS snapshot problem, and the fix is ordering or an explicit resolver. Both failing points at routing or policy, and the next check is whether the app runs inside a work profile with its own network restrictions.

Finally, know which guest you are testing on. Very old emulator images shipped without the tun support that a guest-side VPN client needs, so no profile could ever work there; current images include it. A client that fails on a current image is a client or profile problem, and rebuilding the host tunnel will not change the outcome.

Related articles