What We Tested and How
Comparing providers requires a consistent methodology. The closest equivalent to a 4 GB RAM / 2 vCPU instance in each provider's Frankfurt or Amsterdam region is a reasonable baseline. Running benchmarks at different hours (02:00, 10:00, 18:00 UTC) helps catch noisy-neighbor effects during peak load.
Disk I/O is typically measured with fio using a 4K random read/write mix. Network throughput can be tested with iperf3 against a fixed reference server. CPU benchmarks using sysbench with 10,000,000 prime calculations reveal single-threaded and multi-threaded behavior.
Providers covered here: Hetzner Cloud, Vultr (Frankfurt), DigitalOcean (Frankfurt), Linode/Akamai (Frankfurt), Contabo, OVHcloud VPS, Ionos, and IONOS VPS Flex. Two of these - Contabo and Ionos - are worth flagging early due to reports of inconsistent disk I/O variance, 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 its reputation for disk performance is well established. Its NVMe-backed instances are designed to match or exceed what comparable plans from providers costing significantly more can offer. Sysbench results on Hetzner's 2 vCPU instances are consistently competitive.
What stands out beyond raw numbers is consistency. Hetzner's I/O variance tends to be low, which matters when you are running PostgreSQL or Redis and need predictable latency. Network throughput on the 1 Gbps interface is generally clean and close to the rated ceiling.
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 a strong 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 disk I/O tends to run behind Hetzner, 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. A 3-node cluster with Kubernetes 1.30 provisions its control plane in a few 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
Round-trip latency from Frankfurt-region nodes to cities across Europe varies by provider peering. Vultr tends to edge 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, a few milliseconds 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. Network-attached volumes will deliver noticeably lower IOPS than local NVMe, which is expected. They are appropriate for databases that do not require extreme I/O, backups, and shared mount points across multiple nodes.
Vultr Block Storage shows similar characteristics to Hetzner Volumes on equivalent configurations. 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. Build artifacts, log archives, and static assets are all appropriate use cases.
Avoid Contabo for any I/O-heavy workload. Reports of high IOPS variance between runs and occasional instance unresponsiveness are a recurring concern. 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. Go binaries tend to perform close to x86 levels on Arm64, and the price-to-compute ratio is generally favorable.
# 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'