I recently wrote about my experience of setting up a dedicated server at Hetzner. I leased those machines with the goal of running KVM on them to provide many VMs (Virtual Machines).
NOTE: This article has been updated (2020-11-10) for Ubuntu 20.04.1 LTS.
Actually getting the network configured correctly on both the KVM Host (server) and in the KVM Guests for Hetzner's network proved to be quite tricky, so I thought I would write it up here.
Previously, when I used servers at SoYouStart, the process was simply to buy a block of IP addresses, and create a Virtual Mac address for each one in their Control Panel, after which you would configure each guest VM's NIC with the appropriate IP and Virtual Mac address. Hetzner do not offer Virtual Mac addresses, and so instead a static routing approach is required. This is not better or worse in-particular than SoYouStart, it is just different to what I had become accustomed to ;-)
My Hetzner Server's NIC:
My Hetzner Server's MAC:
My Hetzner Server's IP:
Hetzner's Gateway for my Server:
I purchased an additional IPv4 address block from Hetzner to use for my VMs:
220.127.116.11/28. This means that the usable IP addresses for the VMs will be from
18.104.22.168 through to
We will create a bridged network setup, and each Guest VM will forward its traffic via the host, the following diagram gives a basic outline.
Configuring the KVM Host
If you did not read my previous post, then our server is running Ubuntu 19.04 and we have already installed the KVM Ubuntu packages by running:
sudo apt-get -y install qemu-kvm libvirt-daemon-system libvirt-clients bridge-utils uvtool.
First, we need to modify the servers network configuration to create the bridge interface. The configuration file is
/etc/netplan/01-netcfg.yaml and the out-of-the-box config for my server looks like:
### Hetzner Online GmbH installimage network: version: 2 renderer: networkd ethernets: mainif: match: macaddress: 10:20:30:40:50:60 addresses: - 22.214.171.124/32 - 2a01:2a01:2a01:2a01::2/64 routes: - on-link: true to: 0.0.0.0/0 via: 126.96.36.199 gateway6: ee60::1 nameservers: addresses: - 188.8.131.52 - 184.108.40.206 - 220.127.116.11 - 2a01:4f8:0:1::add:9999 - 2a01:4f8:0:1::add:1010 - 2a01:4f8:0:1::add:9898
In case a mistake is made when modifying the network config, it is a good idea to make a backup of the original config:
cp /etc/netplan/01-netcfg.yaml /etc/netplan/01-netcfg.yaml.BAK.
We will edit the netplan config file to create the bridge like so:
network: version: 2 renderer: networkd ethernets: enp4s0: match: macaddress: 10:20:30:40:50:60 dhcp4: no dhcp6: no bridges: br0: macaddress: 10:20:30:40:50:60 interfaces: - enp4s0 dhcp4: no dhcp6: no addresses: - 18.104.22.168/32 - 2a01:2a01:2a01:2a01::2/64 routes: - to: 0.0.0.0/0 via: 22.214.171.124 on-link: true - to: 126.96.36.199/28 scope: link - to: "::/0" via: "ee60::1" on-link: true nameservers: addresses: - 188.8.131.52 - 184.108.40.206 - 220.127.116.11 - 2a01:4f8:0:1::add:1010 - 2a01:4f8:0:1::add:9999 - 2a01:4f8:0:1::add:9898
NOTE: There are two key things about the above configuration:
- The bridge interface
br0must have the same MAC address as the physical
enp4s0interface, otherwise Hetzner will not route the traffic from the server.
- We have added a static routing rule for the
br0interface for the subnet
18.104.22.168/28that we purchased for our VMs. Without this we will not be able to send IP traffic to our VMs.
To apply the network changes simply run
sudo netplan apply. If anything goes horribly wrong and you can no longer SSH to the server, you will need to arrange to use the Hetzner remote physical console (also called a KVM) to fix the network config. Once you have access you can start by copying the backup of the netplan config back into place and running netplan apply.
Next we must enable IP forwarding, so that the traffic from our new subnet is routed via the servers main IP address, to do this run:
sysctl -w net.ipv4.conf.all.forwarding=1. However, for this change to be persisted across reboots, we also need to edit the file
/etc/sysctl.conf and within there set
Remember that UFW firewall that we enabled in the previous post? We need to instruct it to allow the IP forwarding, otherwise we will not be able to communicate with out VMs via TCP/IP. This is done by running the command:
sudo ufw route allow in on br0 out on br0.
Creating a KVM Guest VM
When creating a guest VM, we will use the Ubuntu Cloud images and
As part of the VM setup, I want to specify the network settings that will be used by the Operating System within that VM. To do that, I would like to supply a Cloudinit config for netplan, unfortunately the versions of uvtool shipped before Ubuntu 19.10 do not support that. We can however patch our uvtool to support this. If you are using Ubuntu 19.10 or later you can skip this step. This step only needs to be done once:
cd /tmp wget http://static.adamretter.org.uk/uvtool-cloudinit.patch sudo patch /usr/lib/python2.7/dist-packages/uvtool/libvirt/kvm.py /tmp/uvtool-cloudinit.patch
We will now setup a new Guest VM, we will call it
guest1. First, we generate an SSH key to use when connecting to our new Guest VM:
mkdir ~/guest-ssh-keys chmod 700 ~/guest-ssh-keys ssh-keygen -b 4096 -C "ubuntu@guest1" -f ~/guest-ssh-keys/guest1
Secondly, we will create a netplan configuration for the Guest VMs network settings:
cat > /tmp/guest1-netplan << EOL version: 2 ethernets: enp1s0: addresses: - 22.214.171.124/28 gateway4: 126.96.36.199 nameservers: addresses: - 188.8.131.52 - 184.108.40.206 - 220.127.116.11 routes: - to: 18.104.22.168/32 via: 0.0.0.0 scope: link EOL
NOTE: There are two key things about the above configuration:
- The guest's network configuration file does not start with the key
network:, as it is passed as the
- The gateway and route in the above configuration are set to the IP address of our Host (dedicated server). Our host will forward the IP traffic so that its source has the correct MAC address for Hetzner to route it.
- The interface name
enp1s0is correct for Ubuntu 20.04.1 LTS, in previous versions of Ubuntu, this should be defined as
ens3. It seems the interfaces were renamed due to changes in newer versions of Systemd :-/.
Before we use
uvtool to create our VM, we want to make sure that we are using the latest Ubuntu Cloud images, so we refresh our images:
sudo uvt-simplestreams-libvirt sync --source=http://cloud-images.ubuntu.com/minimal/releases arch=amd64 release=disco
Finally, we can now use
uvtool to create our VM:
uvt-kvm create \ --ssh-public-key-file ~/guest-ssh-keys/guest1.pub \ --memory 4096 \ --disk 40 \ --cpu 2 \ --bridge br0 \ --network-config /tmp/guest1-netplan \ --packages language-pack-en,openssh-server,mosh,git,vim,puppet,screen,ufw \ guest1 arch="amd64" release=disco label="minimal release"
All being well, running
virsh list --all should show our newly created and running VM:
$ virsh list --all Id Name State ----------------------------------------- 1 guest1 running
Now that our Guest VM is up and running, we can connect to it using the SSH key we created earlier:
ssh -i ~/guest-ssh-keys/guest1 firstname.lastname@example.org
And that's it, we now have our first Guest VM running on KVM on our Hetzner dedicated server.