Pricing: What You Actually Pay

Hetzner bills hourly, capped at a monthly maximum. The CX22 (2 vCPU, 4 GB RAM) runs €0.006/hour, never exceeding €3.92/month. The CX32 (4 vCPU, 8 GB RAM) sits at €6.83/month. Compare that to a Hetzner-equivalent on DigitalOcean or Linode - you are looking at 2x to 3x the cost for equivalent specs.

The dedicated CPU line (CCX) removes the shared-CPU risk. A CCX23 (4 dedicated vCPU, 16 GB RAM) costs €22.49/month. AWS would charge roughly $90-120/month for comparable dedicated compute in eu-central-1. On our test server running a PostgreSQL 16 primary with 12 GB working set, the CCX23 handled 8,400 TPS on pgbench with a scale factor of 100 - acceptable for most production OLTP loads.

Traffic pricing is where Hetzner genuinely differentiates. Each instance gets a generous traffic allowance (20 TB on CX22). Overage is €1.19/TB outbound. We never triggered overage in twelve months across moderately trafficked services.

# Hetzner CLI - list current server types and prices
hcloud server-type list

# Example output snippet:
# NAME     CORES  MEMORY  DISK  STORAGE_TYPE  PRICE/HOUR
# cx22     2      4.0 GB  40 GB NVMe          €0.0060
# cx32     4      8.0 GB  80 GB NVMe          €0.0095
# ccx23    4      16.0 GB 160 GB NVMe         €0.0313

Network Performance and the Bandwidth Ceiling

Hetzner's internal network between servers in the same datacenter is fast. We measured 9.4 Gbps between two CX32 instances in nbg1 using iperf3. Inter-datacenter traffic between nbg1 (Nuremberg) and hel1 (Helsinki) dropped to around 940 Mbps, which is acceptable for replication but not for latency-sensitive synchronous writes.

Public network performance is where expectations need calibration. Consumer-facing bandwidth from a single CX instance saturates around 1 Gbps under sustained load. That is fine for most applications. If you need 10 Gbps public egress per node, look at dedicated servers instead - Hetzner's Robot platform offers exactly that for colocated hardware starting around €40/month.

Hetzner's private networking (vSwitch and private network features) works reliably. We used it to isolate database nodes from public access entirely. Setup via hcloud CLI takes under two minutes.

# Create a private network and attach two servers
hcloud network create --name prod-internal --ip-range 10.0.0.0/16
hcloud network add-subnet prod-internal \
  --network-zone eu-central \
  --type server \
  --ip-range 10.0.1.0/24

hcloud server attach-to-network db-primary --network prod-internal --ip 10.0.1.2
hcloud server attach-to-network db-replica --network prod-internal --ip 10.0.1.3

# Test internal throughput
iperf3 -s  # on db-replica
iperf3 -c 10.0.1.3 -t 30 -P 4  # on db-primary

Disk I/O: NVMe That Actually Delivers

The NVMe storage on shared CX instances is competitive. We ran fio against a fresh CX32 volume with a 4K random read workload and got 87,000 IOPS. Writes came in at 54,000 IOPS. Sequential read hit 1.2 GB/s. These are not enterprise SAN numbers but they are better than many cloud providers charge premium prices to deliver.

Volume storage (Hetzner Volumes, network-attached block storage) is a different story. Latency on volumes averages 0.8-1.2 ms versus 0.1-0.2 ms on local NVMe. For databases, always use local NVMe where possible. Use volumes for backups, object-like workloads, or data that survives server deletion.

On the CCX dedicated line, local NVMe IOPS stay consistent under sustained load - no noisy neighbor effect. On shared CX instances, we saw occasional IOPS drops to 60% of peak during business hours in nbg1. Not catastrophic, but worth knowing if you run latency-sensitive workloads.

# fio test used on CX32 (4K random read, queue depth 32)
fio --name=randread \
  --ioengine=libaio \
  --rw=randread \
  --bs=4k \
  --direct=1 \
  --size=4G \
  --numjobs=4 \
  --iodepth=32 \
  --runtime=60 \
  --group_reporting

# Our result on CX32 local NVMe:
# READ: bw=340MiB/s (357MB/s), IOPS=87.1k
// advertisement

API and Automation: hcloud CLI and Terraform

The hcloud API is clean and well-documented. The CLI is installable via a single binary and covers 95% of what you need day to day. The Terraform provider (hetznercloud/hcloud) is mature - we used it to manage a 40-node cluster with no provider bugs encountered in twelve months.

The API rate limit is 3,600 requests per hour per token. For teams running aggressive automation or CI/CD pipelines that spin up and destroy servers frequently, this limit is reachable. We hit it once during a load-testing run that destroyed and recreated 20 servers in a loop. The solution is to cache state locally and batch operations.

For teams building heavier DevOps automation on top of Hetzner, tools like TaskbotsHub (taskbotshub.ai) can bridge the gap between Hetzner's API and more complex multi-cloud orchestration workflows - particularly useful when you need conditional provisioning logic that goes beyond what Terraform handles cleanly. The Hetzner provider also supports user_data for cloud-init, which covers most bootstrap requirements natively.

# Terraform: minimal Hetzner server with cloud-init
terraform {
  required_providers {
    hcloud = {
      source  = "hetznercloud/hcloud"
      version = "~> 1.47"
    }
  }
}

resource "hcloud_server" "web" {
  name        = "web-01"
  image       = "ubuntu-24.04"
  server_type = "cx32"
  location    = "nbg1"
  ssh_keys    = [hcloud_ssh_key.default.id]

  user_data = file("cloud-init.yaml")

  network {
    network_id = hcloud_network.prod.id
    ip         = "10.0.1.10"
  }
}

Object Storage: S3-Compatible but Region-Limited

Hetzner Object Storage launched in 2023 and uses an S3-compatible API. Pricing is €0.0056/GB/month with 1 TB free egress per month to the internet. That egress allowance is significant - AWS charges $0.09/GB after the first 100 GB.

The catch: Object Storage is available in only three locations (Nuremberg, Falkenstein, Helsinki as of mid-2025). No US or Asia-Pacific presence. If your users are global, you will need a CDN in front. We used Cloudflare R2 caching on top of Hetzner Object Storage for a media-heavy application and kept costs under €12/month for 800 GB stored.

The S3 API compatibility is good enough for rclone, s3cmd, and the AWS SDK without modification. We ran restic backups directly to Hetzner Object Storage with no issues.

# rclone config for Hetzner Object Storage
# Add to ~/.config/rclone/rclone.conf
[hetzner-s3]
type = s3
provider = Other
env_auth = false
access_key_id = YOUR_ACCESS_KEY
secret_access_key = YOUR_SECRET_KEY
endpoint = https://nbg1.your-objectstorage.com
acl = private

# Test upload
rclone copy ./backup-2025-06-23.tar.gz hetzner-s3:my-backups/

# restic to Hetzner Object Storage
export AWS_ACCESS_KEY_ID=YOUR_ACCESS_KEY
export AWS_SECRET_ACCESS_KEY=YOUR_SECRET_KEY
restic -r s3:https://nbg1.your-objectstorage.com/my-backups init

Where Hetzner Falls Short

Managed databases are absent. There is no Hetzner-managed PostgreSQL, MySQL, or Redis equivalent of RDS or DigitalOcean Managed Databases. You run your own, which is fine for experienced teams but adds operational overhead. We ran Patroni on three CCX23 nodes for HA PostgreSQL - it works, but you own the failure modes.

Load balancers are available but limited. Hetzner Load Balancers top out at 200,000 concurrent connections on the LB31 tier (€28.49/month). No WAF, no DDoS scrubbing at the LB level. If you need L7 features or advanced traffic shaping, you are fronting with Cloudflare or building your own HAProxy layer.

Support is email-only for the cloud product. There is no phone support, no dedicated account manager unless you are on an enterprise contract. Response times on tickets were 4-18 hours in our experience - adequate for non-critical issues, not acceptable if your database cluster is down. Plan your monitoring and runbooks accordingly; do not expect Hetzner support to rescue an outage.

The geographic footprint is Europe-centric. US locations exist (Ashburn, Virginia) but the selection of server types is narrower there and pricing loses some of the European advantage. Asia-Pacific coverage is nonexistent. Multi-region global deployments will require supplementing Hetzner with another provider.

# Patroni minimal config for Hetzner 3-node PostgreSQL HA
# /etc/patroni/config.yml
scope: pg-prod
namespace: /service/
name: pg-primary

restapi:
  listen: 10.0.1.2:8008
  connect_address: 10.0.1.2:8008

etcd3:
  hosts:
    - 10.0.1.2:2379
    - 10.0.1.3:2379
    - 10.0.1.4:2379

bootstrap:
  dcs:
    ttl: 30
    loop_wait: 10
    retry_timeout: 10
    maximum_lag_on_failover: 1048576

postgresql:
  listen: 0.0.0.0:5432
  connect_address: 10.0.1.2:5432
  data_dir: /var/lib/postgresql/16/main
// advertisement

Reliability: What the Uptime Actually Looks Like

Hetzner publishes a status page at status.hetzner.com. Over twelve months we tracked three incidents affecting our instances: two were brief (under 15 minutes) network blips in fsn1, one was a 47-minute degraded storage event in nbg1 that slowed disk I/O to 30% of normal without taking instances offline. No full outages on any of our nodes.

Hetzner does not publish an SLA percentage for cloud compute in the same explicit way AWS does. Their terms reference 99.9% availability as a target. In practice, our measured uptime across 40 node-months was 99.97%. That is better than the stated target and competitive with providers charging 3x the price.

The Nuremberg and Falkenstein datacenters share physical proximity - roughly 80 km apart. This matters for disaster recovery planning. A regional event affecting both simultaneously is possible. Helsinki (hel1) provides genuine geographic separation for EU workloads.

# Monitoring Hetzner uptime with a simple cron check
# Add to /etc/cron.d/hetzner-check
*/5 * * * * root curl -sf --max-time 5 https://api.hetzner.cloud/v1/servers \
  -H "Authorization: Bearer $HCLOUD_TOKEN" \
  | jq '.servers[] | select(.status != "running") | .name' \
  >> /var/log/hetzner-anomalies.log 2>&1

Project Setup and Naming on Hetzner

Hetzner organizes resources under Projects, and you get separate API tokens per project. This is useful for isolating staging from production and for giving contractors scoped access without touching billing.

One operational detail worth mentioning: naming servers, networks, and volumes consistently from day one saves significant toil later. We use a pattern of environment-role-index (prod-pg-01, staging-api-02). If you are also registering domains alongside your infrastructure setup, services like nicename.me can help you identify clean, available names for projects or subdomains before you commit to an internal naming scheme - particularly useful when you want your service hostname and domain to match without conflicts.

Hetzner Projects also support Labels, which are key-value tags on any resource. These are queryable via the API and useful for cost attribution or dynamic inventory generation for Ansible.

# Generate Ansible dynamic inventory from Hetzner labels
hcloud server list -l env=prod -o json \
  | jq -r '.[] | .public_net.ipv4.ip' \
  > /tmp/prod-hosts.txt

# Or use the official Hetzner Ansible inventory plugin
# ansible.cfg
[inventory]
enable_plugins = community.hrobot.hcloud

# hcloud_inventory.yml
plugin: hcloud
token: "{{ lookup('env', 'HCLOUD_TOKEN') }}"
groups:
  webservers: "'web' in name"
  databases: "'pg' in name"