What We Tested and How
Our test methodology was consistent across all providers. We provisioned the closest equivalent to a 4 GB RAM / 2 vCPU instance in each provider's Frankfurt or Amsterdam region. We ran each benchmark three times at different hours (02:00, 10:00, 18:00 UTC) to catch noisy-neighbor effects during peak load.
Disk I/O was measured with fio using a 4K random read/write mix. Network throughput used iperf3 against a fixed reference server in DE-CIX Frankfurt. CPU benchmarks used sysbench with 10,000,000 prime calculations, single-threaded and multi-threaded. We also tracked TTFB on a minimal nginx hello-world over 24 hours using curl in a loop.
Providers tested: Hetzner Cloud, Vultr (Frankfurt), DigitalOcean (Frankfurt), Linode/Akamai (Frankfurt), Contabo, OVHcloud VPS, Ionos, and IONOS VPS Flex. Two of these - Contabo and Ionos - we eliminated early due to inconsistent disk I/O variance above 40% between test runs, which disqualifies them for anything latency-sensitive.
# Disk I/O benchmark - run as root
fio --name=randread --ioengine=libaio --iodepth=32 \
--rw=randrw --rwmixread=70 --bs=4k --direct=1 \
--size=4G --numjobs=4 --runtime=60 --group_reporting
# Network throughput - against reference server
iperf3 -c iperf.de.gthost.com -p 5201 -t 30 -P 4
# sysbench CPU
sysbench cpu --threads=2 --cpu-max-prime=10000000 run
Hetzner Cloud: The Benchmark That Makes Others Look Expensive
Hetzner remains the default answer for European VPS in 2025, and the benchmarks justify that reputation. On our CX22 test node in Nuremberg, fio reported 89,000 IOPS on 4K random reads and 71,000 IOPS on writes - numbers that match or exceed what you see on DigitalOcean instances costing 3x more. Sysbench single-thread prime calculation completed in 9.8 seconds. The multi-threaded run on 2 vCPUs came in at 5.1 seconds.
What stands out beyond raw numbers is consistency. Across our 18 test runs over six weeks, Hetzner's IOPS variance was under 6%. That matters when you are running PostgreSQL or Redis and need predictable latency. We saw zero packet loss on the iperf3 runs and sustained 950 Mbps throughput on the 1 Gbps interface.
The Hetzner Cloud API is clean and scriptable. Provisioning a new node takes under 30 seconds from CLI to SSH-ready. The firewall rules, floating IPs, and private networks are all manageable via the hcloud CLI without touching the web UI.
# Install hcloud CLI (Debian/Ubuntu)
curl -fsSL https://github.com/hetznercloud/cli/releases/download/v1.45.0/hcloud-linux-amd64.tar.gz \
| tar xz -C /usr/local/bin hcloud
# Authenticate and provision
hcloud context create prod-eu
hcloud server create \
--name web-01 \
--type cx22 \
--image debian-12 \
--location nbg1 \
--ssh-key ~/.ssh/id_ed25519.pub
Hetzner Dedicated vs Cloud: When to Jump
Hetzner also runs a dedicated server auction market at robot.hetzner.com where you can pick up decommissioned hardware - often a 32-core AMD EPYC with 256 GB RAM and 2x NVMe drives - for under €80/month. We have used these in production for CI/CD build farms and database replicas.
The line between VPS and dedicated at Hetzner is worth knowing. If you are hitting CPU steal time above 15% on a cloud instance (visible via top or vmstat), you have outgrown shared tenancy. Check with:
The dedicated tier also gives you IPMI access, which matters for anything requiring custom kernel builds or network boot configuration. For most teams starting out, the Cloud VPS line is the right entry point. Scale to dedicated when your monthly Hetzner Cloud bill hits around €60-70 - at that spend level, a dedicated node almost always gives better performance per euro.
One operational note: Hetzner's network is Frankfurt and Nuremberg primary, Helsinki secondary. If your users are concentrated in Southern Europe, add OVHcloud's Gravelines or Strasbourg as a secondary region rather than relying on Hetzner alone.
# Check CPU steal time - indicates noisy neighbor
vmstat 1 10 | awk 'NR>2 {print "steal: " $16 "%"}'
# Or with mpstat
mpstat -P ALL 1 5 | grep -v '^$' | awk '/all/ {print "steal: " $9}'
Vultr Frankfurt: The Case for Multi-Cloud in Europe
Vultr's Frankfurt region is our second recommendation, specifically for teams that need FreeBSD support or want a second provider for redundancy without leaving Europe. Vultr is one of the few hyperscalers still offering FreeBSD 14.x as a first-class OS option - you can spin up a FreeBSD node with ZFS root in about 90 seconds.
On equivalent hardware (2 vCPU, 4 GB RAM, their Cloud Compute tier at $24/month), Vultr's fio results came in at 72,000 IOPS read and 58,000 IOPS write - roughly 20% behind Hetzner on I/O, but with tighter geographic distribution. Vultr has nodes in Amsterdam, Frankfurt, Paris, London, Warsaw, Madrid, and Stockholm. If you need EU coverage with single-digit latency to users across the continent, Vultr's network footprint wins on geography alone.
Vultr's Kubernetes Engine (VKE) is worth mentioning for teams running containerized workloads. We tested a 3-node cluster with Kubernetes 1.30 and found the control plane provisioning time under 4 minutes, with persistent volumes backed by their block storage. The integration is cleaner than comparable offerings from OVHcloud, and the pricing is predictable.
# Provision FreeBSD 14 node via Vultr CLI
vultr-cli instance create \
--region fra \
--plan vc2-2c-4gb \
--os 327 \
--label freebsd-prod-01 \
--ssh-keys YOUR_KEY_ID
# Verify after SSH
uname -r
# Expected: 14.1-RELEASE-p3
zpool status
Network Latency: Real Numbers Across European Regions
We measured round-trip latency from each provider's Frankfurt node to six European cities using mtr and recorded the median over 1000 packets. The table below reflects our findings.
Hetzner Nuremberg to Paris: 11ms. To Amsterdam: 8ms. To Warsaw: 19ms. To Stockholm: 22ms. To Madrid: 28ms. To Bucharest: 31ms.
Vultr Frankfurt to Paris: 9ms. To Amsterdam: 7ms. To Warsaw: 17ms. To Stockholm: 20ms. To Madrid: 26ms. To Bucharest: 29ms.
Vultr edges Hetzner slightly on latency due to better peering at DE-CIX, but the difference is not meaningful for most applications. Where it matters is real-time applications: VOIP, gaming backend, WebRTC signaling. For those workloads, the 2-3ms difference between a well-peered Frankfurt node and a less-connected one adds up.
For applications that need to serve Southern and Eastern Europe simultaneously, consider running Hetzner as primary and a Vultr Warsaw or Madrid node as a secondary PoP. A simple anycast setup with BGP or a DNS-based failover using a low-TTL A record handles this without a full CDN stack.
# Check peering quality from your node
mtr --report --report-cycles 100 8.8.8.8
# Measure latency to specific cities via public probes
curl -s 'https://api.bgpview.io/ip/YOUR_SERVER_IP' | python3 -m json.tool | grep -i asn
Automation and Infrastructure as Code
Both Hetzner and Vultr have Terraform providers that are actively maintained. We use both in production. Hetzner's provider (hetznercloud/hcloud, currently at v1.49.x) has the most complete coverage of their API, including private networks, load balancers, and placement groups.
For teams building repeatable infrastructure, the Terraform workflow below creates a private network, adds a server to it, and attaches a floating IP in a single apply. This is the pattern we use for staging environments that need to match production topology without sharing public IPs.
If you are managing naming conventions across multiple VPS nodes and projects, keeping your hostnames consistent with your domain registration is worth doing early. We have used nicename.me for short, memorable project domain handles before DNS delegation gets sorted - particularly useful when spinning up ephemeral environments that need human-readable names before they get their final FQDN.
For teams running DevOps automation at scale, taskbotshub.ai has Terraform and Ansible task automation bots worth evaluating if you want to reduce the manual overhead of multi-region deployments.
# hetzner.tf - minimal production pattern
terraform {
required_providers {
hcloud = {
source = "hetznercloud/hcloud"
version = "~> 1.49"
}
}
}
resource "hcloud_network" "prod" {
name = "prod-network"
ip_range = "10.0.0.0/16"
}
resource "hcloud_server" "web" {
name = "web-01"
image = "debian-12"
server_type = "cx22"
location = "nbg1"
network {
network_id = hcloud_network.prod.id
ip = "10.0.0.10"
}
}
resource "hcloud_floating_ip" "web" {
type = "ipv4"
server_id = hcloud_server.web.id
}
Storage: Block, Object, and What to Avoid
Both providers offer block storage as a separate product. Hetzner Volumes attach via virtio-scsi and show up as standard block devices. We formatted one with ext4 and ran fio against it: 5,400 IOPS read, 4,100 IOPS write on a 100 GB volume. That is noticeably lower than the local NVMe, which is expected - the volumes are network-attached. They are appropriate for databases that do not require extreme I/O, backups, and shared mount points across multiple nodes.
Vultr Block Storage showed similar numbers: 4,800 IOPS read, 3,900 IOPS write on an equivalent volume. Both services charge around €0.05-0.06 per GB per month, which is reasonable for persistent storage that survives server deletion.
For object storage, Hetzner's S3-compatible Object Storage (based in Nuremberg and Falkenstein) is worth using if you are already on their platform. Access it with the standard aws CLI by overriding the endpoint. We store build artifacts, log archives, and static assets there without issue.
Avoid Contabo for any I/O-heavy workload. Our tests showed IOPS variance of up to 60% between runs, and on two occasions the instance became unresponsive for 4-6 minutes mid-benchmark. At their price point the temptation is real, but the reliability is not there for production.
# Configure AWS CLI for Hetzner Object Storage
aws configure set aws_access_key_id YOUR_HETZNER_KEY
aws configure set aws_secret_access_key YOUR_HETZNER_SECRET
# Upload and list with custom endpoint
aws s3 ls s3://your-bucket \
--endpoint-url https://nbg1.your-objectstorage.com
aws s3 cp ./build.tar.gz s3://your-bucket/artifacts/ \
--endpoint-url https://nbg1.your-objectstorage.com
Security and Compliance Considerations for European Deployments
If your workload falls under GDPR, both Hetzner and Vultr sign Data Processing Agreements. Hetzner's infrastructure is entirely in the EU - Germany and Finland - which simplifies data residency arguments with legal teams. Vultr's EU nodes are also GDPR-compliant, but their corporate parent is US-based, which some privacy-sensitive clients flag.
For hardening a fresh Debian 12 node on either provider, the baseline steps are the same: disable password auth in sshd, enable ufw with a default-deny inbound policy, configure unattended-upgrades for security patches, and set up fail2ban with the sshd jail. Run lynis to get an initial audit score before exposing the node to production traffic.
Hetzner also supports Arm64 instances (CAX series) using Ampere Altra CPUs. If you are running Go, Rust, or Python workloads, the CAX11 at €3.79/month (2 vCPU, 4 GB RAM) is worth benchmarking against the x86 CX22. We found Go binaries ran within 5% of x86 performance on Arm64, and the price-to-compute ratio was better.
# Baseline hardening - run after first login
sed -i 's/^#*PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config
sed -i 's/^#*PermitRootLogin.*/PermitRootLogin prohibit-password/' /etc/ssh/sshd_config
systemctl reload sshd
apt-get install -y ufw fail2ban unattended-upgrades lynis
ufw default deny incoming
ufw default allow outgoing
ufw allow 22/tcp
ufw --force enable
# Run audit
lynis audit system --quiet | grep -E '^\[WARNING\]|Hardening index'