Skip to main content

Quarter of the Bill, Two-Thirds of the Network: What a Six-Cloud Kubernetes Benchmark Means for Your Hetzner Node Pool

13 min readDora NodaDora Noda
Share
On this page

Ankra AI ran the same Kubernetes benchmark suite on six clouds in August 2026 — UpCloud, OVH, DigitalOcean, then AWS, GCP, and Hetzner — and the headline number is almost offensively simple: Hetzner's 2-vCPU worker costs about a quarter of anything else in the test while carrying storage that embarrasses nodes six times its price. The catch is equally simple: it gives up two-thirds of the network throughput and the dedicated-CPU class to get there.

If you run a self-hosted PaaS on Cluster API with Hetzner as the infrastructure provider, that tradeoff is not trivia. It is the single decision that sets your per-tenant unit cost. Here is the verdict up front, then the numbers behind it, then what to actually put in your default node pool.

Cloud (2 vCPU / 4 GB worker)Worker cost/moPod-to-pod networkDisk storyCPU class
Hetzner CX23 (hel1)2 × €5.49 (~$13 total)1.85 Gbit/s fwdLocal NVMe: 2.5–3.6 GB/s seq, ~30k IOPSShared/burstable
GKE e2-medium + fee2 × $24 + $73 fee ($121 total)1.73 Gbit/s fwdpd-balanced cap: 175 MB/s, ~3.7k IOPSShared (187m schedulable!)
EKS t3.medium + fee2 × $34 + $73 fee ($141 total)4.97 Gbit/s fwdEBS gp3 baseline: 135 MB/s, ~3.1k IOPSBurstable (unlimited mode)
UpCloud 2xCPU-4GB~$20–30/node5.40 Gbit/s fwdNet block, QoS-capped: 420 MB/s, 100k IOPSDedicated share
OVH c3-4~$20–30/node3.61 Gbit/s fwdLocal NVMe: up to 7.5 GB/s, 20k capDedicated (cpu-optimized)
DigitalOcean s-2vcpu-4gb~$20–30/node1.76 Gbit/s fwdLocal SSD, inconsistent twinsShared vCPU

Hetzner is the value verdict. EKS is the best hyperscaler showing. GKE's entry tier is a demo tier, not a workload tier. And "2 vCPU" turns out to be two entirely different products wearing the same label, with a 7x performance gap between them. The rest of this post is about which of those facts should change your fleet.

The price math, recomputed after Hetzner's 2026 hikes

The first objection to any Hetzner cost argument in 2026 is that Hetzner raised prices — twice. The April adjustment lifted the entry CX23 from €2.99 to €3.99, and the June 15 repricing took it to €5.49, with the shared-AMD CPX and dedicated-CCX lines hit far harder (CPX22 went from €7.99 to €19.49, a 2.44x jump; entry CCX13 from €15.99 to €42.99, a 2.69x jump). "Cheap Hetzner" is not as cheap as the 2024 memes say.

It does not matter to the verdict. Two CX23 workers at €5.49 each cost roughly €11 per month before VAT — about $13. The equivalent two workers on EKS run about $68 in compute plus a $73 control-plane fee, totaling ~$141. GKE lands near ~$121 the same way.

That is a 9–11x gap at the worker-plus-control-plane level, and even against the mid-tier Europeans at $20–30 per node, Hetzner costs roughly a quarter. The benchmark's own phrasing holds: a quarter of anything else here.

Two honest caveats. First, the hyperscaler fee buys a real managed control plane, while the Hetzner number in the test is self-managed k3s — on a Cluster API fleet you already pay that control-plane cost in management-cluster machines and your own on-call attention, so price it, don't ignore it.

Second, the CPX/CCX hikes mean the "just buy a bigger Hetzner box" escape hatch is narrower than it was: upsizing out of CX into dedicated Hetzner silicon now costs multiples, not increments. Size the default pool right the first time, because the step up is no longer cheap.

For a concrete fleet translation: ten CX23-class workers (a plausible small multi-tenant pool) cost ~€55/month in raw compute. The same ten vCPU-pairs on EKS-class pricing clear $600/month before egress. That delta — roughly an order of magnitude — is the entire economic argument for a Hetzner-backed PaaS, and the benchmark confirms the hardware underneath it is not a toy.

Network: there are two kinds of ceilings, and only one is benign

The iperf3 phase forced client and server pods onto different nodes and measured pod-to-pod TCP through the overlay, 30 seconds per direction. EKS won outright at 4.97 Gbit/s forward — within 8% of UpCloud's round-one best of 5.40. Hetzner managed 1.85 forward and 1.41 reverse; GKE did 1.73 and 1.82. Hence "two-thirds less": Hetzner gives up roughly 60–65% of the leader's throughput.

But the ranking is the least interesting row. The interesting row is "what limits it":

  • Hetzner: receiver CPU at ~70%. The 2-vCPU box is saturating on packet processing. This is the benign ceiling — it scales with instance size, so buying a bigger node widens the pipe. Capacity planning advice: linear and predictable.
  • EKS: receiver CPU at ~48%, 4.97 Gbit/s. Headroom to spare — but the t3.medium network is burst-rated, so a 30-second benchmark rides the burst window while a sustained streaming workload eventually meets a much lower baseline. Great for bursty tenant HTTP, misleading for anything that streams.
  • GKE: provider policer, 40k+ retransmits with idle CPUs. Same signature DigitalOcean showed in round one. No bigger node fixes this at this tier; the cap is in the provider's fabric, not your box.

Same benchmark, opposite capacity-planning advice, indistinguishable on any spec sheet. For a PaaS default pool, the translation is direct: if your tenants serve bursty web traffic (they do — that is what a git-push PaaS hosts), Hetzner's CPU-bound 1.85 Gbit/s is fine and grows when you upsize.

If a tenant needs sustained multi-gigabit east-west — large image pulls on every deploy, cross-AZ-style replication chatter, video egress — that is the workload that justifies either bigger Hetzner nodes or a non-Hetzner pool, and you should be able to name the tenant before you pay for it.

One more consideration for fleet builders: per-tenant bandwidth on a shared-node PaaS is a noisy-neighbor vector. A provider-capped network (GKE, DigitalOcean) accidentally solves noisy-neighbor throttling for you. On Hetzner, the ceiling is your node's CPU, which means one tenant's traffic spike eats packet-processing cycles shared with every other tenant on the box.

Budget for Cilium bandwidth-manager annotations or per-pod rate limits in your default tenant profile — the benchmark's ceiling analysis is also your fairness analysis.

Storage: the cheapest node in the test has the most embarrassing disk — for everyone else

This is the section that breaks the "cheapest box is the worst box" assumption. Full per-worker fio table for round two:

Per workerHetzner w0/w1GKE w0/w1EKS w0/w1
Seq write3,609 / 2,452 MB/s175 / 175 MB/s135 / 135 MB/s
Seq read2,742 / 2,853 MB/s176 / 177 MB/s132 / 132 MB/s
4k rand read35,769 / 33,742 IOPS3,769 / 3,7593,099 / 3,099
4k rand write29,489 / 28,612 IOPS3,572 / 3,5583,033 / 3,034
fdatasync p992.21 / 2.47 ms3.39 / 3.46 ms3.52 / 3.52 ms

Read the hyperscaler columns twice: GKE's 175 next to 175, EKS's 3,099 next to 3,099. That is what a QoS cap looks like — pd-balanced and gp3 baseline doing exactly what their documentation promises, no more, ever. Predictable, plannable, and roughly a tenth of what the budget tier delivers elsewhere.

Meanwhile €5.49 of Hetzner local NVMe posts 2.5–3.6 GB/s sequential and ~30k random IOPS: 10–20x the throughput of disks attached to nodes six times its price.

Three things a fleet operator must do with this, none of which is "declare victory":

  1. Durability is your replication story. Hetzner and OVH local NVMe is instance-local — lose the VM, lose the disk. The hyperscaler tax partly buys network-attached durability that survives the node. On a CAPH fleet, local-NVMe workers are correct for dev clusters, CI runners, batch work, image-layer caches, and any replicated data layer where you control durability (etcd with 3 members, Postgres with streaming replicas).

    They are wrong as the only place an unreplicated tenant volume lives. This is a placement rule, not a reason to pay 10x.

  2. Mind the etcd 10 ms bar. etcd's guidance wants fdatasync p99 under 10 ms, and every round-two node clears it — but round one showed DigitalOcean's entry worker breaching at 11.86 ms on a cluster that passed every HTTP SLO. A node can look immaculate from outside while quietly degrading an etcd member or a Postgres primary.

    Gate new machine types with an fio fdatasync check (Sonobuoy-style conformance plus a 5-minute fio job) before tenants schedule onto them.

  3. Caps are a feature for noisy neighbors. UpCloud's 100,120-next-to-100,119 IOPS columns are a QoS cap you can plan around — and throttle against. Local NVMe has no governor: one tenant's checkpoint dump can starve the disk for everyone on the box. Pair fast local disks with per-tenant I/O expectations in your quota story.

CPU: "2 vCPU" is two products with a 7x gap

Sysbench across both rounds draws the sharpest line in the whole exercise: every shared or burstable tier lands between 1,069 and 1,370 events per second; every dedicated-share tier lands between 7,970 and 9,666. No middle. The spec sheets all say "2 vCPU"; the benchmark says there are two products with a 7x gap wearing the same label.

EKS's t3.medium tops the budget class (unlimited-mode bursting helps, and it posted the best memory bandwidth of the trio at 4.9 GB/s), but sustained bursting on t3 bills you for the privilege — the meter keeps running after the burst window closes.

For node-pool sizing, the sharper lesson is GKE's: the e2-medium node exposes 940m of allocatable CPU, of which GKE's own system pods request 753m, leaving 187 schedulable millicores on a "2 vCPU" node. Ankra's stock manifests would not schedule at all until requests were forked down from 250m to 50m. If you size any managed entry-class pool from the spec sheet, Kubernetes will refuse most of what you put on it — you are buying a fifth of the node you think you are.

Audit allocatable-vs-requested on your own default MachineDeployment the same way (kubectl describe node, subtract kubelet + CNI + CSI + monitoring reservations) and set tenant default requests against the remainder, not the instance label. On self-managed CAPH workers this reservation is yours to tune, which is an argument for self-managed: every millicore the managed control plane's DaemonSets would have eaten stays schedulable.

The twin-node check is the final CPU footnote with real scheduler consequences: all three round-two pairs are tight twins (Hetzner ±5%, GKE ±2%, EKS ±1%), but round one caught DigitalOcean's identical twins 2.8x apart under contention. The Kubernetes scheduler sees identical allocatable on both and will happily place your latency-critical pod on the slow twin.

Prefer instance families with dedicated shares for any pool that hosts tail-sensitive tenants, and treat shared-tier pools as explicitly best-effort in your tenant-facing docs.

What to actually put in your CAPH node pool

Translated from the benchmark's "what to place where" into fleet shape — a default pool plus two exception pools, which maps cleanly onto three CAPH MachineDeployments:

PoolInstance shapeHostsWhy the benchmark says so
Default tenant poolHetzner shared (CX/CAX class)Stateless web services, workers, PR previews, CIQuarter cost; HTTP latency holds the line (all SLOs passed, 0.00% errors); CPU ceiling is benign and scales with size
Stateful / control-plane poolDedicated-share (UpCloud-style, or Hetzner CCX if priced in)etcd members, Postgres primaries, anything with an fsync SLOetcd 10 ms bar + twin consistency + capped-IOPS predictability; never shared-tier for quorum members
Burst-egress poolHigh-network (EKS-class or bigger Hetzner nodes)Sustained-streaming tenants, heavy image-pull workloadsOnly pool where per-tenant bandwidth justifies the premium; name the tenant before paying

Three rules hold the design together. First, HTTP latency is not the differentiator — every cluster in both rounds passed all eight k6 SLO thresholds with zero errors, from €5.49 Hetzner to managed EKS. Buy on network ceilings, disk caps, and CPU class, not on web latency.

Second, replicate across the durability gap: local NVMe's 10x disk wins only count with 3x etcd and streaming Postgres replicas absorbing node loss, plus Velero or Volume Group Snapshots on a schedule.

Third, re-run the suite on your own metal: Ankra's suite is three Kubernetes Jobs and a target deployment, ~15 minutes per cluster, and the marginal cost of an answer is one YAML declaration plus coffee. Their caveat applies to you too — one day, two workers, one size class, burst windows included — so treat their numbers as the shape of the tradeoff and your own run as the sizing input.

One disclosure for honest reading: Ankra sells a Kubernetes platform, and benchmarking through its own provisioning path is also a demo of that path (it found three of their own onboarding bugs along the way, to their credit). The numbers that matter — iperf3, fio, sysbench — are provider-side physics, not Ankra-side marketing, and the methodology (same k3s, same CNI, same suite, same day) is the right design.

Believe the benchmark about what "2 vCPU" means; verify it on your own pool before you sign the node contract.

The bottom line

A self-hosted PaaS on Hetzner is not "cheap because it is worse." At the measured 2-vCPU tier it is cheaper while being better at disks, adequate at bursty web latency, narrower at sustained network, and honestly labeled at CPU once you read allocatable instead of the instance name. That is a tradeoff you can engineer around with pool separation — default shared pool for stateless tenants, dedicated pool for state, replication over every local disk — instead of a premium you pay to avoid thinking. Quarter of the bill, two-thirds of the network, ten times the disk: pick the pool that matches the tenant, and let the benchmark, not the pricing page, tell you which one that is.

Bex.co is the open-source, AI-native Render alternative — push a git repo, get a running HTTPS service on machines you own. Star the repo on GitHub or deploy your first app today.

Related articles

Run this on infrastructure you own

bex is the open-source, AI-native Render alternative — push a git repo and get a running HTTPS service on your own machines.

Get started with bex