Ordering and the Hetzner Robot Interface

Hetzner sells dedicated servers through their Robot panel at robot.hetzner.com, not through the Cloud Console. The two products share an account but almost nothing else. Dedicated servers in the Server Auction (now called the server market) can drop 30-50% below list price for hardware that is six to eighteen months old - we picked up an AX61 with 64 GB RAM and 2x 512 GB NVMe for €39/month this way.

After payment clears, provisioning takes between 15 minutes and 6 hours depending on whether your server needs hardware swaps. You will get an email with your server IP, root rescue password, and a link to the Robot panel entry. The first thing to do is log into the Robot, navigate to your server, and set a permanent rescue password under the 'Rescue' tab before you do anything else - this is your out-of-band access mechanism.

Hetzner does not provide IPMI in the traditional sense for most models. The AX series uses a software rescue system: you activate rescue mode from the Robot panel, then SSH into the server IP with the temporary root password. The server boots into a minimal Linux environment from Hetzner's infrastructure. For servers in the EX and SX lines, some models do have IPMI/iDRAC access available as an add-on, but expect a 24-48 hour provisioning delay and an extra €9-15/month.

The Robot API is REST-based and documented at robot.your-server.de/doc/webservice/en.html. Authentication is HTTP Basic over HTTPS. If you are building automation pipelines around server provisioning, the API covers rescue mode activation, reboot triggers, and IP management, but it does not cover the server market auction workflow - that requires human interaction.

# Activate rescue mode via Robot API
curl -u 'robot-user:robot-password' \
  -d 'os=linux&arch=64&authorized_key=your-ssh-key-fingerprint' \
  https://robot-ws.your-server.de/boot/65.21.x.x/rescue

# Then trigger a hardware reset
curl -u 'robot-user:robot-password' \
  -d 'type=hw' \
  -X POST \
  https://robot-ws.your-server.de/reset/65.21.x.x

Rescue Mode and Manual OS Installation

Hetzner's rescue environment runs Debian 12 by default as of 2025. Once you SSH in, you have access to the full disk layout and Hetzner's installimage script, which is the path of least resistance for most installs.

Running `installimage` launches a TUI that lets you select OS image, configure RAID level, partition layout, and hostname. The images are maintained by Hetzner and lag behind upstream releases by two to four weeks typically. For Debian 12, Ubuntu 24.04, CentOS Stream 9, and Rocky Linux 9 this is fine. If you need a custom kernel or a distribution not in their list, skip installimage entirely and use debootstrap or an ISO mount workaround.

For a manual Debian install without installimage, the process is: partition disks with fdisk/parted, create your RAID array with mdadm, format, mount, run debootstrap, chroot, install grub. We have done this for NixOS deployments where installimage obviously has no image. The rescue environment has enough tools to make it work.

One non-obvious issue: the AX series servers with NVMe drives present the drives as /dev/nvme0n1 and /dev/nvme1n1. The installimage script handles these correctly, but if you are partitioning manually, remember that NVMe partitions are /dev/nvme0n1p1 not /dev/nvme0n1p1 - obvious to NVMe veterans but a source of confusion if you are coming from SATA-only experience. Also, grub-install on NVMe requires the drive path without partition number: `grub-install /dev/nvme0n1`.

# Inside rescue mode - check what disks you have
lsblk -d -o NAME,SIZE,MODEL,ROTA

# Example output on AX102:
# nvme0n1  1.8T  SAMSUNG MZQL21T9HCJR   0
# nvme1n1  1.8T  SAMSUNG MZQL21T9HCJR   0

# Run installimage interactively
installimage

# Or use a config file for automation
cat > /tmp/install.conf << 'EOF'
DRIVE1 /dev/nvme0n1
DRIVE2 /dev/nvme1n1
SWRAID 1
BOOTLOADER grub
HOSTNAME prod-db-01.example.com
PARTITION /boot ext4 1G
PARTITION lvm vg0 all
LV vg0 root / ext4 50G
LV vg0 swap swap swap 16G
LV vg0 data /data xfs all
IMAGE /root/.oldroot/nfs/install/../images/Debian-bookworm-amd64-base.tar.gz
EOF
installimage -a -c /tmp/install.conf

Software RAID Configuration

Hetzner's installimage configures mdadm RAID when you set SWRAID to 1. For production workloads we always use RAID 1 on the OS drives even if we have additional storage volumes. The default chunk size and algorithm choices from installimage are reasonable for most workloads.

After the OS installs and you reboot into it, verify the array state immediately. A degraded array that you discover two weeks later during a disk failure is a bad day.

For servers with more than two drives, RAID 10 gives you better sequential read performance than RAID 5/6 and avoids the write hole problem. On the SX line, which has six or more drive bays, RAID 6 with a hot spare is also reasonable if you value capacity over rebuild speed. RAID 6 rebuild times on spinning rust at Hetzner scale can exceed 48 hours.

One mdadm behavior that catches people: after a clean install, the array will still show as 'clean' even if it has never finished a full sync. Check `/sys/block/md0/md/sync_action` and `/proc/mdstat` to see actual sync status. Do not pull a drive to test failover until the initial sync completes.

# Check RAID status after first boot
cat /proc/mdstat

# Expected output for healthy RAID 1:
# md1 : active raid1 nvme1n1p3[1] nvme0n1p3[0]
#       1873612800 blocks super 1.2 [2/2] [UU]
#       bitmap: 0/14 pages [0KB], 65536KB chunk

# Check sync completion percentage
watch -n2 cat /proc/mdstat

# Configure mdadm for email alerts on failure
echo 'MAILADDR root@example.com' >> /etc/mdadm/mdadm.conf
echo 'MAILFROM mdadm@prod-db-01.example.com' >> /etc/mdadm/mdadm.conf
mdadm --monitor --daemonise --mail=root@example.com --delay=300 /dev/md1

# Check array details
mdadm --detail /dev/md1
// advertisement

Network Configuration and Hetzner's IP Model

Every Hetzner dedicated server gets one public IPv4 address and a /64 IPv6 subnet. The IPv4 is single-homed. If you need additional IPs, you purchase them separately at €1.18/month each, with a limit of three additional IPs per server by default. Subnets of /29 or /28 are available but require a justification request through support.

Hetzner's network is dual-stack by default. The IPv6 address for your server is your prefix with ::2 as the host part. Your gateway is your prefix with ::1. On Debian 12, the interfaces come up with DHCP for IPv4 and SLAAC for IPv6 by default when installed via installimage.

VLAN support on dedicated servers requires the Hetzner vSwitch product. A vSwitch is a private Layer 2 segment that can connect dedicated servers to each other and to Hetzner Cloud instances in the same project. The vSwitch itself is free; you pay for the servers. This is how you build hybrid dedicated/cloud architectures without routing traffic over the public internet.

To connect a dedicated server to a vSwitch, you configure a VLAN interface on the server side and enable the server in the Robot panel under the vSwitch settings. Traffic on the vSwitch does not count against bandwidth quotas.

Hetzner's upstream bandwidth is 1 Gbps for most dedicated servers, with 10 Gbps available on the EX and SX lines for a premium. Our AX102 tests showed sustained 940 Mbps in both directions to Frankfurt-based servers. Latency from Hetzner Frankfurt to Amsterdam averaged 11ms, to Paris 14ms, to London 17ms in our measurements.

For servers running Kubernetes or any overlay network, the vSwitch MTU is 1400 bytes to account for VXLAN overhead. Set this explicitly or your pod-to-pod traffic will silently fragment.

# Add VLAN interface for vSwitch (VLAN ID 4000 in this example)
ip link add link eth0 name eth0.4000 type vlan id 4000
ip link set eth0.4000 mtu 1400
ip addr add 10.0.1.1/24 dev eth0.4000
ip link set eth0.4000 up

# Make persistent on Debian 12 with /etc/network/interfaces
cat >> /etc/network/interfaces << 'EOF'
auto eth0.4000
iface eth0.4000 inet static
    address 10.0.1.1
    netmask 255.255.255.0
    vlan-raw-device eth0
    mtu 1400
EOF

# Verify bandwidth with iperf3 between two servers
# On receiver:
iperf3 -s
# On sender:
iperf3 -c 65.21.x.x -P 8 -t 30

Storage Performance: Real Numbers

We benchmarked the NVMe drives on an AX102 using fio 3.36 on Debian 12.5 with kernel 6.6.13. These are the numbers you actually care about when deciding whether to use the local NVMe or attach additional cloud storage.

Sequential read peaked at 6.8 GB/s per drive, which is in line with Samsung's spec sheet for the PM9A3 used in this generation. RAID 1 read throughput hit 11.2 GB/s because mdadm stripes reads across both mirrors. Sequential write on RAID 1 is limited by the slower drive, which gave us 5.1 GB/s sustained.

Random 4K read at queue depth 32 delivered 890K IOPS. Random 4K write delivered 210K IOPS. These numbers drop significantly under RAID 1 write because both mirrors must acknowledge - at QD32 we measured 105K IOPS for random 4K write, exactly half as expected.

For database workloads specifically: PostgreSQL 17 on this hardware running pgbench at scale factor 1000 with 16 clients reached 48,000 TPS in read-write mode. That is with shared_buffers=32GB, effective_cache_size=96GB, and random_page_cost=1.0 for the NVMe. Hetzner's local NVMe is fast enough that you should set random_page_cost to 1.0-1.1, not the default 4.0.

Hetzner also offers Volume storage - network-attached block devices that attach to your server over iSCSI. Volumes are capped at 500 MB/s throughput and 10K IOPS in our tests. They are useful for bulk storage or backups but not for anything latency-sensitive. Latency on Volumes was 0.8-1.2ms at idle versus 0.08ms for local NVMe.

# fio test for 4K random read/write
fio --name=rand-rw \
    --ioengine=libaio \
    --direct=1 \
    --bs=4k \
    --iodepth=32 \
    --numjobs=4 \
    --rw=randrw \
    --rwmixread=70 \
    --size=10G \
    --runtime=60 \
    --time_based \
    --group_reporting \
    --filename=/dev/md1

# Sequential throughput test
fio --name=seq-read \
    --ioengine=libaio \
    --direct=1 \
    --bs=1M \
    --iodepth=16 \
    --numjobs=1 \
    --rw=read \
    --size=50G \
    --runtime=30 \
    --time_based \
    --group_reporting \
    --filename=/dev/md1

Automating Server Setup with Cloud-Init and Ansible

Hetzner's installimage does not support cloud-init natively, but once your base OS is installed you can bring cloud-init in immediately. For a repeatable provisioning workflow, we use a thin installimage config to get the OS on disk, then an Ansible playbook that runs on first SSH connection to handle everything else.

The installimage config sets the hostname, RAID layout, and partition scheme. For naming conventions, we use a systematic approach: environment prefix, role, datacenter, and sequence number. A name like `prod-db-fsn1-01.example.com` is immediately parseable. If you are registering domain names for your infrastructure, nicename.me has a clean API for bulk domain registration that integrates well into provisioning scripts - useful when you are spinning up isolated environments that each need their own DNS delegation.

For teams adopting AI-assisted DevOps, taskbotshub.ai provides automation bots that can hook into the Hetzner Robot API to handle provisioning triggers based on monitoring thresholds or schedule events - we tested their Hetzner integration for auto-provisioning standby nodes when primary load exceeded 80% for 10 consecutive minutes.

The Ansible inventory for a Hetzner fleet should use dynamic inventory via the hetzner.hcloud collection, even for dedicated servers. The collection supports dedicated servers through the Robot API as of version 3.0.0.

# Install hetzner.hcloud Ansible collection
ansible-galaxy collection install hetzner.hcloud:>=3.0.0

# Example playbook for first-boot hardening
---
- name: Harden Hetzner dedicated server
  hosts: new_servers
  become: true
  tasks:
    - name: Set hostname
      hostname:
        name: "{{ inventory_hostname }}"

    - name: Install base packages
      apt:
        name:
          - fail2ban
          - unattended-upgrades
          - prometheus-node-exporter
          - smartmontools
        state: present
        update_cache: true

    - name: Configure sshd - disable password auth
      lineinfile:
        path: /etc/ssh/sshd_config
        regexp: '^#?PasswordAuthentication'
        line: 'PasswordAuthentication no'
        state: present
      notify: restart sshd

    - name: Enable smartd monitoring
      systemd:
        name: smartd
        enabled: true
        state: started

    - name: Schedule weekly RAID check
      cron:
        name: "mdadm check"
        weekday: "0"
        hour: "2"
        minute: "0"
        job: "echo check > /sys/block/md1/md/sync_action"

  handlers:
    - name: restart sshd
      systemd:
        name: ssh
        state: restarted
// advertisement

Firewall: nftables on Hetzner Dedicated

Hetzner provides a hardware firewall through the Robot panel, but it is limited - basic stateless packet filtering with no stateful tracking and no IPv6 support as of early 2026. Use it as a first layer, but run nftables or iptables on the host for anything real.

On Debian 12, nftables is the default and iptables is a compatibility shim. Write native nftables rules. The base ruleset for a public-facing server should drop everything by default and explicitly allow only what you need.

One Hetzner-specific consideration: their network infrastructure sends ICMP for path MTU discovery. Do not drop ICMP type 3 code 4 (fragmentation needed) or you will get mysterious connection stalls on connections from outside Hetzner's network. This is a common mistake that results in hard-to-debug issues where large responses silently fail.

Hetzner's abuse team is responsive but reactive. If your server gets compromised and starts sending spam or running DDoS amplification, they will null-route your IP within hours. Run fail2ban, set up rate limiting in nftables for connection attempts, and monitor /var/log/auth.log. We saw an unprotected server attract 40,000 SSH login attempts in the first 6 hours of being online with port 22 open.

# /etc/nftables.conf - production baseline
table inet filter {
    chain input {
        type filter hook input priority 0;
        policy drop;

        # Allow established connections
        ct state established,related accept

        # Allow loopback
        iif lo accept

        # Allow ICMP (IPv4 and IPv6) - including fragmentation needed
        ip protocol icmp icmp type {
            echo-request, echo-reply,
            destination-unreachable, time-exceeded
        } accept
        ip6 nexthdr icmpv6 accept

        # SSH - rate limited
        tcp dport 22 ct state new \
            limit rate 10/minute \
            burst 20 packets accept

        # Application ports
        tcp dport { 80, 443 } accept

        # Prometheus node exporter - internal only
        ip saddr 10.0.1.0/24 tcp dport 9100 accept
    }

    chain output {
        type filter hook output priority 0;
        policy accept;
    }

    chain forward {
        type filter hook forward priority 0;
        policy drop;
    }
}

# Apply and persist
nft -f /etc/nftables.conf
systemctl enable nftables

Monitoring, Alerts, and the Hetzner SLA Reality

Hetzner's dedicated server SLA is 99.9% uptime, which allows for 8.7 hours of downtime per year. In practice, the hardware failure rate we observed over six months was lower than that - one server had a RAM stick fail and Hetzner replaced it within 4 hours of our support ticket. Their support for hardware issues is genuinely fast compared to providers charging five times the price.

For monitoring, deploy Prometheus node-exporter on every server and scrape it from a central Prometheus instance. The node-exporter 1.8+ includes NVMe smart data via the nvme_info collector if you enable it with `--collector.nvme`. Add the mdadm collector for RAID state: `--collector.mdadm`.

Disk health on NVMe requires different SMART attributes than spinning rust. Watch for Unsafe_Shutdowns, Media_and_Data_Integrity_Errors, and Available_Spare. A drop in Available_Spare below 10% means the drive is approaching end-of-life and should be replaced proactively.

Hetzner does not provide any managed backup service for dedicated servers. Their Storage Box product gives you SFTP/FTP/Samba/Rsync access to a remote storage volume and is useful for offsite backups. A 10 TB Storage Box is €34/month. We use restic with the SFTP backend to back up to Storage Box from cron, with daily snapshots retained for 30 days and weekly for 12 weeks.

# Start node-exporter with all useful collectors enabled
/usr/bin/prometheus-node-exporter \
  --collector.nvme \
  --collector.mdadm \
  --collector.systemd \
  --collector.processes \
  --web.listen-address=":9100"

# Check NVMe SMART data manually
nvme smart-log /dev/nvme0n1 | grep -E \
  'available_spare|media_errors|unsafe_shutdowns|data_units_written'

# restic backup to Hetzner Storage Box
export RESTIC_REPOSITORY="sftp:u123456@u123456.your-storagebox.de:/backups/prod-db-01"
export RESTIC_PASSWORD_FILE=/etc/restic/password

restic backup /data /etc /home \
  --exclude=/data/tmp \
  --tag daily

# Retention policy
restic forget \
  --keep-daily 30 \
  --keep-weekly 12 \
  --keep-monthly 6 \
  --prune

Cost Comparison: Dedicated vs Cloud at Scale

The AX102 at €89/month gives you 14 vCPUs, 128 GB RAM, and 3.84 TB NVMe raw. An equivalent AWS instance is an r7a.4xlarge at 16 vCPU and 128 GB RAM: $806/month on-demand, or around $490/month with a one-year reserved instance. You are paying 4-5x more on AWS for comparable or slightly better CPU performance and significantly worse storage IOPS.

The cost advantage narrows as you add managed services. RDS Multi-AZ, managed Kubernetes control plane, CloudWatch logging, NAT gateways - these add up and on Hetzner you manage them yourself. The break-even point in our analysis is around three servers: below that, AWS's operational overhead savings outweigh Hetzner's cost advantage if your team has fewer than two experienced sysadmins. At five or more servers, Hetzner dedicated wins on pure economics unless you are in a heavily regulated environment requiring specific compliance certifications that AWS holds and Hetzner does not.

Hetzner has ISO 27001, SOC 2 Type II, and is certified for GDPR data processing in Germany and Finland. They do not hold FedRAMP authorization, so US government workloads are a non-starter on Hetzner.

Network egress on Hetzner is free up to 20 TB/month per server on the AX line. AWS charges $0.09/GB after 100 GB free. At 20 TB of egress, AWS network cost alone is $1,800/month. For media delivery or any bulk data transfer workload, the network pricing difference alone justifies Hetzner.

// advertisement