Six checks, run in order, decide whether a VM can act as the VPN gateway for its own host. The hypervisor does none of this for you: the guest owns the tunnel, the host owns a route, and the network between them is ordinary routing. I keep the list because skipping one step always produces the same symptom, silence, where the host pings the guest happily and reaches nothing internal.

Start with the reason. Either the corporate client only installs inside the guest, or policy keeps it there, and you still need the internal build server from the host. If the host is allowed its own client, do that instead: one tunnel on the host beats two networks plus a NAT every time.

Check 1: the host needs a link it can address

The host must own an interface sitting in the same prefix as the guest's second adapter. Host-only in VirtualBox, host-only or a custom VMnet in VMware Workstation, an internal bridge or tap device in QEMU/KVM, and an Internal switch in Hyper-V all qualify. Guest 192.168.56.2, host 192.168.56.1, prefix 192.168.56.0/24 is the layout I use in labs.

QEMU user-mode networking (-netdev user, the default SLIRP backend) and VirtualBox NAT fail this check outright. Guest packets reach a proxy process on the host, so the host never sees a device carrying the guest's addresses and has nothing to point a route at. Hyper-V's Default Switch is the same story. If ping 192.168.56.2 from the host fails, stop: no route added later will repair it.

Check 2: the guest forwards and translates

Packets from the host arrive with a host-only source address and an internal destination, so the guest has to forward them and then hide them behind its tunnel address. On a Linux guest that means forwarding, a FORWARD accept pair, and source NAT toward the tunnel:

sudo sysctl -w net.ipv4.ip_forward=1
sudo iptables -A FORWARD -i eth1 -o tun0 -j ACCEPT
sudo iptables -A FORWARD -i tun0 -o eth1 -m state --state RELATED,ESTABLISHED -j ACCEPT
sudo iptables -t nat -A POSTROUTING -s 192.168.56.0/24 -o tun0 -j MASQUERADE

The MASQUERADE rule is the part people leave out. Corporate routers rarely know that 192.168.56.0/24 exists; with the rule, internal servers see the guest's tunnel address and answer it, and the reply finds its way back to the guest.

A Windows guest is clumsier. IPEnableRouter=1 under SYSTEM\CurrentControlSet\Services\Tcpip\Parameters turns on forwarding without translation, so it only works where corporate routing already points back at your host-only prefix. Internet Connection Sharing does translate, but it renumbers the shared adapter to 192.168.137.1, so read the address it actually took before writing host routes.

Check 3: host routes name the guest, not the tunnel

Send only the prefixes the host needs across the guest. On Linux one route per prefix is enough and reverses cleanly:

sudo ip route add 10.20.0.0/16 via 192.168.56.2
ip route get 10.20.30.40

On Windows the equivalent is route add 10.20.0.0 mask 255.255.0.0 192.168.56.2. Leave out -p while you are still testing, so a bad route dies at reboot, and add it once the path works. Do not remove the host default route unless every host application should use the tunnel; if full-tunnel really is the requirement, add the two /1 routes and keep the on-link route to the hypervisor network:

sudo ip route add 0.0.0.0/1 via 192.168.56.2
sudo ip route add 128.0.0.0/1 via 192.168.56.2

On a Windows host, check the interface metric as well. A route added while the host-only adapter was disabled stays in the table but never carries traffic, and a second adapter with a lower metric can win the lookup for the same prefix.

Check 4: DNS follows the route, not the tunnel default

Routes move packets; the resolver decides which names produce packets. Scope the corporate resolver to the corporate zone instead of replacing the host resolver, and point it at a DNS server the new route already reaches. With systemd-resolved on the host:

resolvectl dns eth1 10.20.0.53
resolvectl domain eth1 '~corp.example.com'

On Windows a name resolution policy table entry does the same job: Add-DnsClientNrptRule -Namespace ".corp.example.com" -NameServers 10.20.0.53. If the guest runs its own resolver on the host-only address, use that address instead, but only after confirming it answers on 192.168.56.2 rather than loopback.

Then check the MTU before blaming DNS for slow pages. The tunnel interface is smaller than the host's 1500-byte interface, so a host sending full-size segments with the DF bit set gets them dropped somewhere inside the guest. The classic symptom is a working ping and an internal web app that stalls after the TLS handshake. Clamp MSS in the guest forward path:

sudo iptables -t mangle -A FORWARD -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu

Check 5: verify from both ends

From the host, traceroute or tracert to an internal address should show 192.168.56.2 as the first hop, not the physical gateway. In the guest, watch the same attempt leave: tcpdump -ni any host 10.20.30.40 shows the forwarded request on the host-only side and the translated one on the tunnel. A request that goes out, comes back, and still leaves the host silent points at the return path.

Two failures account for most of that. Either the guest tunnel carries no route back for the host-only prefix because the NAT rule is missing, or the host route points at an address the guest no longer holds after a restart. Re-read the addresses with ip -br addr and ip route get instead of trusting notes from the last session.

Check 6: make it survive a reboot and a hypervisor upgrade

Persist both halves: netplan or NetworkManager route configuration on the host, route add ... -p on Windows, and net.ipv4.ip_forward=1 in /etc/sysctl.d/ plus a firewall ruleset that reloads in the guest. Snapshot the guest once the configuration verifies, before the next corporate client update lands.

Host-only networks get renumbered when a hypervisor recreates its adapters, which silently invalidates every route and NAT rule built on 192.168.56.0/24. After an upgrade, re-run the first three checks in order; that costs a minute and catches the case where the host still has its route while the guest moved to a new subnet.

IPv6 needs its own pass. ip6tables rules, AAAA records and a second host route family do not follow the IPv4 setup, and a corporate service reachable over IPv6 will look permanently broken while IPv4 works.

Pass or fail, in order: host can ping the guest's host-only address; the guest forwards and MASQUERADEs toward the tunnel; ip route get returns the guest as gateway for the internal prefix; corporate names resolve through the scoped resolver; traceroute shows the guest first; and the same six answers survive a reboot.

Erthyglow kevrennek