VirtualBox VPN Lab: Gateway VM vs Host-Only Network
Decide between a routed gateway VM and a host-only segment for a VirtualBox VPN lab, then make the guest fail closed when the tunnel drops.
You need to decide how much machinery your VPN lab deserves. A routed gateway VM between two VirtualBox segments reproduces the address translation, the return path and the firewall position that a real client sits behind. A single host-only segment gives you one controlled door and nothing else. The gateway version costs three guests and a routing table you maintain; the host-only version costs one guest and a policy. Three questions separate them, and the third decides whether your test can pass at all.
Answer three questions before you clone anything
Start with what the VPN server has to observe. If the server must see a source address that is not the test guest's, something has to translate between them, and that something has to be a router you control. Host-only networking gives the host an adapter on the guest's segment, not a translator. When the guest then needs to reach anything outside, you attach a NAT adapter, and VirtualBox's own NAT engine rewrites the source without asking you.
Next, ask whether the guest may reach anything except through the tunnel. That answer picks internal or host-only, and it is the question most lab write-ups skip. Finally, ask what must happen when the tunnel drops, because that decides policy rather than topology, and a lab that only proves the happy path has tested almost nothing.
| What the test requires | Routed gateway VM | Host-only guest |
|---|---|---|
| Server sees a translated source address | Yes, from your own MASQUERADE rule | No, VirtualBox NAT does it instead |
| The guest has exactly one egress | Yes, while the second adapter faces the tunnel | Only until a NAT adapter is attached |
| Policy is testable during a tunnel failure | Rules live in one place | Rules live on each guest |
Wire the three roles with internal networks
Use three guests and two named segments, with the gateway as the only machine that holds an adapter on both:
VBoxManage modifyvm lab-client --nic1 intnet --intnet1 WAN
VBoxManage modifyvm lab-server --nic1 intnet --intnet1 LAN
VBoxManage modifyvm lab-gw --nic1 intnet --intnet1 WAN --nic2 intnet --intnet2 LAN
There is no separate create step for an internal network. The string in --intnet1 is the segment: two machines share it only if the name matches exactly, and nothing on the host is attached to it. That last property is why the lab repeats cleanly, since the host's routes, its own VPN client and its firewall cannot reach the guests by accident. An internal segment also has no DHCP server unless you add one, so static addresses are the shortest path.
Give the client only its WAN address, with a default gateway pointing at the gateway's WAN address. The server gets a LAN address and uses the gateway's LAN address as its default gateway. A guest that keeps two addresses on one segment, or a stray route to the other segment, has a path around the router, and the tests below will lie to you.
I keep a text file beside the VM names with the four addresses and the two segment names, and paste it into every shell. Weeks later, when a profile change breaks something, that file is the difference between debugging the profile and debugging my own memory.
Pick internal or host-only for the segment that must not leak
Host-only mode creates a virtual adapter on the host itself, usually vboxnet0, and gives the host an address on the guest's segment. That is convenient for administration and fatal for an isolation test, because the host can now reach the guest directly and every host-side service becomes a possible path. Internal mode has no host adapter at all. The two modes are easy to conflate in the network settings panel, so check the documented difference instead of the label (see the VirtualBox networking details page).
For a guest that must use the tunnel, internal is the safer default, with the gateway as the only way out. If you keep host-only, treat the host adapter as a management path and say so out loud: the host may reach the guest, and the guest must not be able to use the host as an egress.
VBoxManage modifyvm lab-client --nic1 hostonly --hostonlyadapter1 vboxnet0
The isolation you can prove is the isolation you configured. A guest with one internal adapter, a default route at the gateway and no NAT adapter has no path that skips the gateway. A guest with a host-only adapter and a NAT adapter has two paths, and only one of them is the one you meant to test.
Route and translate on the gateway, then make it fail closed
VirtualBox does not forward between segments; the guest does. With the VPN server VM behind the gateway, enable forwarding and translate the client's traffic as it leaves toward the server:
sysctl -w net.ipv4.ip_forward=1
iptables -t nat -A POSTROUTING -o eth1 -j MASQUERADE
With that rule the server sees the gateway's LAN address instead of the client's, which is what a real client behind carrier NAT looks like to a head-end. Persist the sysctl value in /etc/sysctl.d/ and the rule in whatever firewall service the distribution uses, or a reboot quietly returns the lab to a routed but untranslated state.
Forwarding is not authorization. Under the default FORWARD policy of ACCEPT, a guest whose default route points at the gateway leaves through whichever interface the routing table picks, tunnel or not. If the gateway also has a NAT adapter for package installs, that adapter is a working unencrypted egress the moment the tunnel drops, and your VPN-only test was never a VPN-only test.
When the server is remote instead and the gateway terminates the tunnel itself, the same decision points at the tunnel interface, not at the NIC that reaches the internet:
iptables -P FORWARD DROP
iptables -A FORWARD -i eth0 -o tun0 -j ACCEPT
iptables -A FORWARD -i tun0 -o eth0 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
Keep the tunnel's own outer transport and an out-of-band management route separate from forwarded traffic. If the only way to repair the gateway is through the tunnel you are debugging, the lab becomes a lockout exercise. A serial console, a host-only management adapter, or a snapshot of a known-good state is cheaper than recovering a broken router by hand.
Verify the path from the guest, not from the host
Work outward. Confirm the client can reach the gateway's WAN address, then the server through the gateway, then that a connection to the VPN port leaves and returns. A capture on both gateway interfaces shows whether the reply arrives and is dropped on the return path, which a capture on the client alone cannot distinguish:
tcpdump -ni eth0 udp port 1194
tcpdump -ni tun0 udp port 1194
Then prove the exit. From the guest, ask an address-reflection service what source it sees and compare that with the gateway's own exit address; if the answer matches the host's address, a path around the tunnel exists. Check which resolver the guest actually uses, and check IPv6 on its own, because a segment where IPv6 is enabled while the tunnel carries only IPv4 leaks by design rather than by accident.
Finally, break it on purpose. Stop the tunnel, or block its outer port, and open a new connection from the guest. It must fail, and the reason must be one you can name: no route, a reject rule, or a dead resolver. Restart the tunnel and confirm the guest recovers without rebuilding the network. If a mesh VPN such as Hamachi or ZeroTier shares the lab, remember that a bridged adapter follows the adapter name, not the mesh, so the bridge has to be repointed after the client installs its own virtual adapter.
VBoxManage list bridgedifs
VBoxManage modifyvm lab-client --bridgeadapter1 <mesh-adapter-name>
Sharing a guest's tunnel with the host mirrors the problem. Pointing the host's default route at the guest works, and so does a proxy inside the guest; NAT through the host does not carry the guest's tunnel for traffic the host originated. Save the segment names and the firewall rules in one place, snapshot the working state, and record the source address the server observed. The next profile change then costs one script run instead of a rebuilt lab.