Hetzner Networking on Proxmox: A Per-Distro Guide
Hetzner's dedicated server networking trips up everyone the first time. The gateway isn't in the same subnet as the IPs you're assigning. Additional IPs are routed individually as /32s, not as a contiguous on-link subnet. The upstream switch MAC-filters by default, so naïve bridged VM setups fail silently.
The fix is conceptually the same on every distro — a routed setup with on-link gateway directives — but every distro decided to do networking differently. This guide covers Proxmox host configuration and per-distro VM configuration for Ubuntu 22.04, Ubuntu 24.04, Debian 12, AlmaLinux 9, and CentOS 7.
Examples use documentation IP space (RFC 5737). Substitute your real values from Hetzner Robot:
- Server primary IP:
192.0.2.10 - Server gateway:
192.0.2.1 - Additional IPs for VMs:
198.51.100.20through198.51.100.25
Why Hetzner is different
In a typical hosting setup, additional IPs come from the same subnet as the host's primary IP, and they share a gateway. On Hetzner:
- Additional IPs are routed individually to your server as /32s, or as a small subnet (e.g., /29) routed via your server's primary IP — not directly attached.
- The gateway address is the host's primary gateway, which is outside the additional IP block entirely.
- The upstream switch performs MAC filtering. Frames with a MAC the switch wasn't told to expect are dropped at the port.
Two ways to deal with MAC filtering for VMs:
- Routed setup (what this guide covers). Proxmox host routes packets to VMs via host routes. VMs use
on-linkto reach the gateway through the host. The switch only ever sees the host's MAC. Works for any number of IPs, no extra Hetzner config needed. - vMAC setup. Request a virtual MAC per additional IP from Hetzner Robot, assign it to the VM, and the switch will accept frames from that MAC. Allows fully bridged networking but adds per-IP operational overhead.
Routed is what most production deployments use. Everything below assumes routed.
Step 1: Proxmox host configuration
Tell the host that your additional IPs are reachable through vmbr0. This causes the kernel to proxy-ARP for them on the host side, which is what gets traffic from the upstream switch into the VMs.
nano /etc/network/interfaces
Add up route add -host lines for each additional IP under the vmbr0 block:
auto vmbr0
iface vmbr0 inet static
address 192.0.2.10/26
gateway 192.0.2.1
bridge-ports eno1
bridge-stp off
bridge-fd 0
# Hetzner additional IPs routed to VMs
up route add -host 198.51.100.20 dev vmbr0
up route add -host 198.51.100.21 dev vmbr0
up route add -host 198.51.100.22 dev vmbr0
up route add -host 198.51.100.23 dev vmbr0
up route add -host 198.51.100.24 dev vmbr0
up route add -host 198.51.100.25 dev vmbr0
For a routed subnet ordered from Hetzner (e.g., a /29), use a single network route instead of per-IP host routes:
up route add -net 198.51.100.16/29 dev vmbr0
Apply without rebooting:
ifreload -a
Verify the routes are active:
ip route | grep vmbr0
Confirm IP forwarding is enabled:
sysctl net.ipv4.ip_forward
It should return 1. If not:
echo 'net.ipv4.ip_forward=1' > /etc/sysctl.d/99-ip-forward.conf
sysctl -p /etc/sysctl.d/99-ip-forward.conf
⚠️ Verify forwarding survives reboot. PVE enables it by default, but hardened systems or sysctl edits can disable it.
Step 2: Disable the Proxmox firewall on Hetzner VMs
⚠️ Uncheck "Firewall" on every VM network interface. PVE's firewall layer interacts badly with Hetzner's switch MAC enforcement and the routed setup. Symptoms range from intermittent packet loss to total outbound failure. Disable it at the VM NIC level and do firewalling inside the guest with nftables or firewalld.
UI path: VM → Hardware → Network Device → Edit → uncheck Firewall.
CLI:
qm set <vmid> --net0 model=virtio,bridge=vmbr0,firewall=0
Step 3: VM configuration per distro
Every example below assumes:
- The VM's primary interface is
ens18(KVM/virtio default; sometimesenp6s18depending on machine type) - The VM gets a single IP:
198.51.100.20/32 - The gateway is
192.0.2.1— the host's gateway, deliberately off-link from the VM's IP
Universal pattern: /32 address, off-link gateway, explicit on-link directive.
Ubuntu 22.04 — netplan
Edit netplan (filename varies; check ls /etc/netplan/):
nano /etc/netplan/50-cloud-init.yaml
network:
version: 2
ethernets:
ens18:
addresses:
- 198.51.100.20/32
nameservers:
addresses:
- 8.8.8.8
- 1.1.1.1
search: []
routes:
- to: 0.0.0.0/0
via: 192.0.2.1
on-link: true
Disable cloud-init network rewriting so it doesn't clobber this on reboot:
cat > /etc/cloud/cloud.cfg.d/99-disable-network-config.cfg <<EOF
network: {config: disabled}
EOF
Apply:
netplan apply
Ubuntu 24.04 — systemd-networkd (skip netplan)
Ubuntu 24's netplan has quirks with on-link routes on point-to-point /32 setups. Skip netplan entirely and write a systemd-networkd unit:
mv /etc/netplan/50-cloud-init.yaml /etc/netplan/50-cloud-init.yaml.disabled
nano /etc/systemd/network/10-ens18.network
[Match]
Name=ens18
[Network]
Address=198.51.100.20/32
DNS=8.8.8.8
DNS=1.1.1.1
[Route]
Gateway=192.0.2.1
GatewayOnLink=yes
Disable cloud-init network management as in the Ubuntu 22 section. Then:
systemctl enable --now systemd-networkd
networkctl reload
Verify with networkctl status ens18 — link state should be routable, route configured.
Debian 12 — systemd-networkd
Debian 12 ships with ifupdown by default. Switch to systemd-networkd for cleaner on-link handling:
systemctl stop networking
systemctl disable networking
nano /etc/systemd/network/10-ens18.network
For multiple IPs on the same interface (e.g., assigning a /29 routed subnet to one VM):
[Match]
Name=ens18
[Network]
Address=198.51.100.20/32
Address=198.51.100.21/32
Address=198.51.100.22/32
DNS=8.8.8.8
DNS=1.1.1.1
[Route]
Gateway=192.0.2.1
GatewayOnLink=yes
systemctl enable --now systemd-networkd
systemctl enable --now systemd-resolved
ln -sf /run/systemd/resolve/stub-resolv.conf /etc/resolv.conf
⚠️ Without systemd-resolved running, the DNS= entries in the .network file are ignored. Either enable resolved (recommended) or write /etc/resolv.conf manually.
AlmaLinux 9 / Rocky 9 — NetworkManager keyfile
NetworkManager stores per-connection config under /etc/NetworkManager/system-connections/. Edit the connection file (interface name on RHEL 9 family is usually enp6s18):
nano /etc/NetworkManager/system-connections/enp6s18.nmconnection
Replace the [ipv4] section with:
[ipv4]
method=manual
address1=198.51.100.20/32
gateway=192.0.2.1
dns=8.8.8.8;1.1.1.1;
route1=192.0.2.1/32,0.0.0.0,0
route1_options=onlink=true
route2=0.0.0.0/0,192.0.2.1,0
route2_options=onlink=true
ignore-auto-routes=true
ignore-auto-dns=true
⚠️ Keyfile permissions matter — NetworkManager refuses to load files with loose permissions:
chmod 600 /etc/NetworkManager/system-connections/enp6s18.nmconnection
chown root:root /etc/NetworkManager/system-connections/enp6s18.nmconnection
Apply:
nmcli connection reload
nmcli connection up enp6s18
Verify with nmcli connection show enp6s18 | grep -E 'IP4|ROUT'.
CentOS 7 — legacy network-scripts
CentOS 7 is EOL but still common in long-lived deployments. Two files to edit:
nano /etc/sysconfig/network-scripts/ifcfg-eth0
DEVICE=eth0
BOOTPROTO=none
ONBOOT=yes
IPADDR=198.51.100.20
PREFIX=32
DNS1=8.8.8.8
DNS2=1.1.1.1
nano /etc/sysconfig/network-scripts/route-eth0
default 192.0.2.1 dev eth0 onlink
Apply:
systemctl restart network
⚠️ CentOS 7 reached EOL in June 2024. No more upstream security updates. New deployments should land on AlmaLinux 9 or Rocky 9. Plan migration timelines for any CentOS 7 VMs you're still maintaining.
Verification
Run from inside the VM after configuration, regardless of distro:
ip addr show # confirm IP is assigned to the right interface
ip route # confirm default route via gateway, onlink
ping -c 3 192.0.2.1 # gateway reachability
ping -c 3 1.1.1.1 # internet connectivity
dig +short google.com # DNS resolution
From outside (your workstation):
ping -c 3 198.51.100.20
ssh root@198.51.100.20
Troubleshooting
VM has correct config but no connectivity
Most common cause: the VM's tap interface isn't attached to vmbr0 on the host. Happens after migrations, manual qm commands, or PVE firewall toggles.
On the host:
brctl show vmbr0
# or with iproute2:
bridge link show | grep vmbr0
Look for tap<VMID>i0. If missing:
ip link set tap<VMID>i0 master vmbr0
Replace <VMID> with the actual VM ID. For a permanent fix, restart the VM cleanly through PVE — the issue usually means PVE thinks the NIC is detached.
Gateway unreachable from VM
ping <gateway> fails from VM, host can reach gateway fine. Walk the chain:
- Host route present?
ip route | grep 198.51.100.20on the host. - IP forwarding on?
sysctl net.ipv4.ip_forwardreturns1. - VM tap on bridge?
brctl show vmbr0. - PVE firewall disabled on the VM NIC? Check VM hardware tab.
- Host iptables FORWARD chain?
iptables -L FORWARD -n -v— look for DROP rules. Common after CSF/UFW configuration on the host. - Inside the VM, is
on-linkactually applied?ip route showshould show the gateway withonlinkflag.
DNS resolves nothing
The DNS client isn't honoring your config:
- systemd-networkd hosts: confirm
systemd-resolvedis active and/etc/resolv.confsymlinks to/run/systemd/resolve/stub-resolv.conf. - NetworkManager hosts:
nmcli dev show <iface> | grep DNSshould list your servers. - Anyone:
cat /etc/resolv.conf. If it lists nothing or stale entries, fix the resolver before debugging anything else.
Intermittent packet loss
Almost always one of:
- PVE firewall enabled on the VM NIC (re-check, it sometimes re-enables after PVE upgrades)
- A second IP on the VM that wasn't routed on the host (VM tries to source-route via the wrong interface)
- vMAC conflict — if you partially enabled vMAC mode in Robot but didn't apply the MAC to the VM
A note on IPv6
Hetzner provides a /64 IPv6 subnet with every server. IPv6 setup is much simpler than IPv4 — no on-link gymnastics, the gateway is the standard link-local fe80::1. Worth setting up alongside IPv4 since dual-stack is essentially free here.
For a VM with IPv6 2001:db8:abcd::20:
[Network]
Address=2001:db8:abcd::20/64
[Route]
Gateway=fe80::1
The host needs net.ipv6.conf.all.forwarding=1 and proxy-ndp configured for the VM's address, or a route directive equivalent to the IPv4 host routes above.
Summary
| Distro | Network tool | Config path |
|---|---|---|
| Ubuntu 22.04 | netplan | /etc/netplan/50-cloud-init.yaml |
| Ubuntu 24.04 | systemd-networkd | /etc/systemd/network/10-ens18.network |
| Debian 12 | systemd-networkd | /etc/systemd/network/10-ens18.network |
| AlmaLinux 9 | NetworkManager keyfile | /etc/NetworkManager/system-connections/enp6s18.nmconnection |
| CentOS 7 | network-scripts (legacy) | /etc/sysconfig/network-scripts/{ifcfg,route}-eth0 |
Host-side configuration is identical across distros. Only the in-guest tooling differs. The pattern — /32 address + off-link gateway + explicit on-link — is what makes Hetzner's routing model work, and it's the one detail every Hetzner setup tutorial needs to get right.