incus-contrib/envs/hetzner/setup/hetzner-setup.md

11 KiB

Hetzner Dedicated Server: Proxmox Setup Guide

This guide covers the manual steps to get a Hetzner dedicated server running Proxmox VE with SSH access from your workstation. Once SSH works, the interactive proxmox-setup script handles everything else (repos, networking, storage, WireGuard, firewall, API tokens).

Workflow:

  1. This guide -- rent a server, install Proxmox via QEMU, get SSH working
  2. proxmox-setup --host <alias> -- configure everything else interactively

1. Server selection

The Hetzner Server Auction offers used dedicated servers at significant discounts. Look for:

  • CPU: Intel with VT-x and VT-d (AMD works too, but Intel nested virtualization is more proven with Proxmox/IncusOS)
  • Cores: 32+ (each IncusOS VM wants 4-8 cores)
  • RAM: 128+ GiB (256 GiB ideal -- 4 VMs at 16 GiB still leaves 192 GiB)
  • Disks: 2+ NVMe or SSD (ZFS mirror for system, extras for VM storage)
  • Network: 1 Gbit/s included, single IPv4

Order the server and wait for provisioning (usually minutes for auction servers). Note the assigned IP address and temporary root password.


2. Proxmox installation

We use the official Proxmox VE ISO installer running inside QEMU from the Hetzner rescue system. This gives the full graphical installer with ZFS support, which is not available via Hetzner's installimage. The method is documented in the Hetzner community tutorial.

2.1 Boot the rescue system

In the Hetzner Robot panel, activate the rescue system (Linux 64-bit) and reboot the server. Then SSH in:

ssh root@<public-ip>

2.2 Discover disks

Identify which disks the server has:

lsblk -d -o NAME,SIZE,MODEL,ROTA,TYPE

Example output on a typical auction server with 2 NVMe drives:

NAME    SIZE MODEL                ROTA TYPE
nvme0n1 477G Samsung SSD 970 EVO     0 disk
nvme1n1 477G Samsung SSD 970 EVO     0 disk

For ZFS RAID1 (mirror), you want two matching disks. NVMe pairs are the strong favourite -- fast and reliable. Note the device names (/dev/nvme0n1, /dev/nvme1n1) for the QEMU command.

If you have additional disks beyond the pair (e.g. large SATA drives), those can be set up as a separate storage pool later. Only pass the system disks to QEMU for now -- proxmox-setup will detect and configure the rest.

2.3 Check BIOS mode

Determine whether the server boots in UEFI or legacy BIOS mode:

[ -d "/sys/firmware/efi" ] && echo "UEFI" || echo "BIOS"

Most modern Hetzner servers are UEFI. The QEMU command below includes the OVMF BIOS line for UEFI -- remove it if your server reports BIOS.

2.4 Download the Proxmox ISO

Get the latest Proxmox VE ISO from the official download page:

wget https://enterprise.proxmox.com/iso/proxmox-ve_9.0-1.iso

Check proxmox.com/en/downloads for the current version and adjust the URL accordingly.

2.5 Set up SSH port forwarding for VNC

On your workstation (not the server), open an SSH tunnel forwarding the VNC port:

ssh -L 5900:localhost:5900 root@<public-ip>

This forwards local port 5900 to the server's localhost:5900, where QEMU will expose its VNC display. Keep this session open.

2.6 Start the QEMU installer

In the SSH session on the server, start QEMU with the ISO and the disks you identified in step 2.2:

qemu-system-x86_64 \
    -enable-kvm \
    -cpu host \
    -m 16G \
    -boot d \
    -cdrom ./proxmox-ve_9.0-1.iso \
    -drive file=/dev/nvme0n1,format=raw,if=virtio \
    -drive file=/dev/nvme1n1,format=raw,if=virtio \
    -bios /usr/share/OVMF/OVMF_CODE.fd \
    -vnc 127.0.0.1:0

Notes:

  • Remove the -bios /usr/share/OVMF/OVMF_CODE.fd \ line for legacy BIOS servers (step 2.3).
  • For SATA drives, use /dev/sda, /dev/sdb instead of /dev/nvmeXn1.
  • Disks appear as /dev/vdX inside the VM because of the virtio interface -- this is normal and expected.
  • The -vnc 127.0.0.1:0 flag binds VNC to localhost only (safe, no password needed since it's behind the SSH tunnel).

2.7 Connect via VNC and install

Open a VNC client on your workstation and connect to 127.0.0.1:5900 (or just 127.0.0.1 -- most clients default to port 5900).

The Proxmox graphical installer appears. Walk through it:

  1. Accept the EULA.
  2. Target disk: Select the ZFS RAID1 (mirror) option across both drives (/dev/vda and /dev/vdb in the VM -- these are your NVMe drives passed through via virtio).
  3. Country/timezone/keyboard: Set as appropriate.
  4. Root password and email: Set a strong root password. This becomes HETZNER_PROXMOX_ROOT_PASSWORD in your env file.
  5. Network: The installer shows a virtualized NIC. Configure it with the server's public IP, gateway, and hostname. This will be corrected in the next step since the real NIC name differs.
  6. Click Install and wait for completion.

The VNC client may disconnect briefly during install -- just reconnect to 127.0.0.1:5900.

2.8 Predict the real network interface name

After installation completes, stop QEMU with Ctrl+C in the SSH terminal. Do not reboot yet -- the network interface name configured by the installer is wrong (it matches the virtual NIC, not the real one).

Use the Hetzner predict-check tool to discover the real interface name:

predict-check

Example output:

eth0 -> enp0s31f6

Note the predicted name (e.g. enp0s31f6). You can also check the current rescue interface for reference:

netdata

2.9 Fix the network config before first real boot

Boot Proxmox again in QEMU, without the ISO (no -cdrom flag):

qemu-system-x86_64 \
    -enable-kvm \
    -cpu host \
    -m 16G \
    -boot d \
    -drive file=/dev/nvme0n1,format=raw,if=virtio \
    -drive file=/dev/nvme1n1,format=raw,if=virtio \
    -bios /usr/share/OVMF/OVMF_CODE.fd \
    -vnc 127.0.0.1:0

Connect via VNC, log in as root, and edit the network configuration:

nano /etc/network/interfaces

Replace the virtual interface name (e.g. ens18) with the predicted real name (e.g. enp0s31f6). A minimal working config:

auto lo
iface lo inet loopback

auto enp0s31f6
iface enp0s31f6 inet static
    address <public-ip>/32
    gateway <gateway-ip>

Save and shut down the VM (shutdown -h now inside the VNC session, or Ctrl+C in the SSH terminal).

2.10 Reboot into Proxmox

Exit the rescue system and reboot the server from the Hetzner Robot panel (or just reboot from SSH). The server now boots from disk into Proxmox with the correct network configuration.

2.11 Verify

ssh root@<public-ip> pvesh get /version
# Should show Proxmox VE version and API info

Alternative method: You can also install Debian 13 via Hetzner's installimage and then upgrade to Proxmox following the official guide. This skips the QEMU/VNC process but does not offer ZFS-on-root from the installer.


3. SSH setup and handoff to proxmox-setup

At this point Proxmox is installed and reachable via its public IP. The remaining configuration (repos, private networking, storage, WireGuard, firewall, API tokens) is handled by the proxmox-setup script. You just need SSH key access and an alias.

3.1 Copy your SSH key

ssh-copy-id root@<public-ip>

3.2 Create an SSH config alias

Add to ~/.ssh/config on your workstation:

Host hetzner-lab
    HostName <public-ip>
    User root
    IdentityFile ~/.ssh/id_ed25519

3.3 Verify SSH alias works

ssh hetzner-lab pvesh get /version

This must succeed before continuing. If it fails, check your SSH key and config entry.

3.4 Run proxmox-setup

# Preview what will be configured
hetzner/proxmox-setup --host hetzner-lab --dry-run

# Run for real (interactive, prompts before each step)
hetzner/proxmox-setup --host hetzner-lab

The script walks through each step interactively:

  1. Detect -- reads host info, block devices, existing config
  2. Repos -- switches to no-subscription repos, removes nag popup
  3. Update -- apt update && apt dist-upgrade
  4. Bridge -- creates vmbr1 (private bridge, 10.10.0.0/24) with NAT
  5. Storage -- detects unused disks, creates ZFS pool, registers with PVE
  6. WireGuard -- installs and configures tunnel, prints client config
  7. Firewall -- locks down public interface (SSH + WireGuard only)
  8. API token -- creates role, pool, and token for incusos-proxmox

Each step prompts for confirmation. Use --yes to skip prompts, or --skip-repos, --skip-storage, etc. to skip individual steps.

At the end, the script prints:

  • A ready-to-use proxmox.yaml config for incusos/targets/hetzner/
  • An env file snippet with the API token
  • A WireGuard client config to save on your workstation

3.5 Connect WireGuard and verify

Save the WireGuard client config printed by the script:

# Linux
sudo wg-quick up hetzner-lab

# macOS -- import into the WireGuard app

Verify access through the tunnel:

ping 10.10.0.1                          # Proxmox host via WireGuard
curl -sk https://10.10.0.1:8006         # Web UI via WireGuard

Optionally add a second SSH alias for access via WireGuard:

Host hetzner-lab-wg
    HostName 10.10.0.1
    User root
    IdentityFile ~/.ssh/id_ed25519

3.6 Test deploy

# Copy and fill in the connection config
cp incusos/targets/hetzner/proxmox.yaml.example incusos/targets/hetzner/proxmox.yaml

# Set the API token
export PROXMOX_TOKEN_SECRET="<token from proxmox-setup output>"

# Dry run -- verify correct bridge, IPs, storage
incusos-proxmox --dry-run \
    --proxmox incusos/targets/hetzner/proxmox.yaml \
    incusos/targets/hetzner/lab-cluster.yaml

Confirm the output shows: vmbr1 bridge, 10.10.0.x IPs, local-zfs storage, 8 cores per VM.


Network architecture (reference)

Once proxmox-setup has completed, the network looks like this:

Internet
    |
    | Public IP (e.g. 5.9.x.x)
    v
[vmbr0] ──── Proxmox host ──── [wg0: 10.10.99.1/24]
                  |                       |
                  |                  WireGuard tunnel
                  |                       |
             [vmbr1: 10.10.0.1/24]   Your workstation
                  |                  (10.10.99.2)
            ┌─────┼─────┐
            |     |     |
          VM-01 VM-02 VM-03
          .101  .102  .103
Interface Purpose
vmbr0 Public (SSH + WireGuard only after firewall)
vmbr1 Private bridge for VMs (10.10.0.0/24, NAT to internet)
wg0 WireGuard tunnel (10.10.99.0/24)

AllowedIPs uses 10.10.0.0/16 to leave room for future subnets (OVN overlay at 10.10.10.0/24, etc.) without updating WireGuard config.