Accessing a web service running on linux VM from host browser

When I created my website, I quickly realized that I don’t want to always deploy to my VPS right away, without checking how it looks and feels first. I needed a way to preview changes locally, which that led to a simple, yet unintuitive question:

How do you view a website that’s running inside a VM on your own PC?

This post is really more of a note to self than anything, though if you are looking to answer the same question, then feel free to read along anyway.

Sidenote on linux/environment

I use debian as my distro of choice on my host, VM and VPS, which kept things nice and simple in my case. Still, most of what’s written here should apply to other linux distros, though some commands, especially package management might vary.

I run my test environment in VirtualBox, if you’re using KVM or another hypervisor, you can still follow along, but you might need to handle some assumed steps yourself, especially in the case of KVM, where port forwarding is less straight forward.

The Problem

My problem was quite simple: Instead of typing IP addresses into the address bar, I just wanted to have something simple, like http://test-server/mywebsite. I’ve also got MariaDB running in my VM, so I’ll need to be able to run queries against the DB in there, as well as be able to SSH in order to do general maintenance tasks. (just some extra fluff, as this website isn’t my only project I’m working on)

Configuring the VM

Quick notes on Bridged Adapter

I’m using a bridged adapter, which means the VM is actually connecting to the host’s physical network interface. If that meant pretty much nothing to you, then you’re not alone. Here’s a breakdown of what that means from a practical point of view:

Inside the VM, this interface should show up as enp0s8, but you should double check with the ip addr command.

Set up the VM to have its own address and talk to the gateway

cat <<EOF | sudo tee /etc/network/interfaces
auto lo
iface lo inet loopback

auto enp0s8
iface enp0s8 inet static
    address 192.168.2.99
    netmask 255.255.255.0
    gateway 192.168.2.254
    dns-nameservers 8.8.8.8 1.1.1.1
EOF

Restart networking when done:

sudo systemctl restart networking

YMMV on what exactly the gateway is, mine is 192.168.2.254 but you should check on your HOST with route -n

This is actually the only piece of configuration needed on the VM, so let’s jump over to the HOST.

Configuring the HOST to talk to the VM

All steps in this section should be done on the HOST machine.

/etc/hosts

We need to teach the HOST how to reach the VM. This is easily done by adding an entry into /etc/hosts.

This is also the time and place to decide what to name the VM: I’ve gone for test-server.

Modify /etc/hosts and add the following line:

192.168.2.99   test-server

This means that test-server should resolve to 192.168.2.99 without even needing a DNS look-up. (you can double check it with a cat /etc/nsswitch.conf | grep hosts command: if files comes first, then it means the system will look inside /etc/hosts for domain name resolutions first)

And that is pretty much it, since we’re basically acting like another device on the network, there’s no need to do anything else.

Going to http://192.168.2.99 on your HOST browser should net you with the apache welcome page, and you should be able to catch yourself in action if you check it with lsof -itcp in the VM:

apache2  1311    www-data   17u  IPv6  17019      0t0  TCP debian-test:http->192.168.2.37:52268 (ESTABLISHED)

Routing traffic between HOST and VM in case the VM is NAT attached

If your VM is NAT attached, you can still do something like this, but there are some extra steps.

First of all, in your VM, your interface will probably be enp0s3, and you do not need (and in fact, should NOT, add a gateway entry, nor a dns-nameserver entry, as the VM itself will act as a gateway. So just leave /etc/network/interfaces as it is in your VM (assuming you’re using NetworkManager).

In NAT attached mode, your VM is hidden from the local network, other than HOST no one else on the LAN can see it. This way, the VM does not have its own IP address, effectively meaning that the VM lives in a private network, so it should have an IP like 10.0.2.x, in my case it is 10.0.2.15.

First of all on HOST’s /etc/hosts we do still want to add a line, but in this instance, we’ll just give localhost an additional new name:

127.0.0.1   test-server

Now, since the VM is hidden from the local network, any services started on the VM will either need to use a different, non-standard port, or we need to find a way to use different ports on the HOST and route the traffic to the standard ports on the VM.

The iptables tool is able to do just that: it allows us to forward traffic from HOST to VM, while also allowing to specify what ports should forward to what.

Executing the following commands should do the trick:

sudo iptables -t nat -A OUTPUT     -p tcp --dport 8080 -d 127.0.0.1 -j DNAT --to-destination 10.0.2.15:80
sudo iptables -t nat -A OUTPUT     -p tcp --dport 10022 -d 127.0.0.1 -j DNAT --to-destination 10.0.2.15:22
sudo iptables -t nat -A OUTPUT     -p tcp --dport 6033 -d 127.0.0.1 -j DNAT --to-destination 10.0.2.15:3306
sudo iptables -t nat -A POSTROUTING -p tcp -d 10.0.2.15 --dport 80 -j MASQUERADE
sudo iptables -t nat -A POSTROUTING -p tcp -d 10.0.2.15 --dport 22 -j MASQUERADE
sudo iptables -t nat -A POSTROUTING -p tcp -d 10.0.2.15 --dport 3306 -j MASQUERADE

Of course there is a lot to unpack here, so let’s break down what these commands actually mean:

How to make the rules permanent?

By default iptables rules vanish on reboot, this can easily be made permanent by installing and using the iptables-persistent package:

sudo apt install iptables-persistent -y
sudo sh -c "iptables-save > /etc/iptables/rules.v4"

Conclusion

Using just a few basic unix tools, you can give your test VM a nice name and stop typing IP addresses and ports into your browser.