UpCloud vs OVH vs DigitalOcean: One Kubernetes Benchmark, Three Different Clouds
On this page
Three providers. Three clusters, provisioned the same morning, each with one control plane and two workers at 2 vCPU and 4 GB per node, all in the same price band of roughly 20 to 30 dollars a month per node. Same Kubernetes distribution, same CNI, same benchmark suite, run phase by phase on the same day.
We described the suite itself, and why it is built the way it is, in the methodology post: a k6 HTTP load test with SLO thresholds, an iperf3 cross-node network phase, and a per-node fio and sysbench phase, all running in-cluster as Kubernetes Jobs. This post is the other half of the story: what the suite actually measured, with the providers named. Four numbers set the scene.
Update, August 9: we have since run the identical suite on EKS, GKE and Hetzner. The six-cloud comparison lives in round two.
One caveat before any conclusions, and it applies to every number below: this is one day, two workers per provider, one size class. At this size DigitalOcean’s entry plan is shared vCPU by design, where UpCloud and OVH sell dedicated-share tiers; the fair fight against DigitalOcean’s dedicated c-2 tier (roughly twice the price) was not part of this run. That asymmetry is inherent to comparing like-priced entry plans, and it turns out to be most of the story.
The contenders
| UpCloud | OVH | DigitalOcean | |
|---|---|---|---|
| Region and plan | fi-hel1, 2xCPU-4GB | EU-WEST-PAR, c3-4 | fra1, s-2vcpu-4gb |
| CPU tier | standard | standard (cpu-optimized line) | basic, shared vCPU |
| Storage | network block (QoS-capped) | local NVMe | local SSD |
| Provisioning | ~10 min, clean | ~9 min, on the third attempt | ~8 min, clean, first try |
All three ran k3s with flannel, provisioned through Ankra with the same declared cluster shape. The provisioning column is worth a sentence: OVH cost us two false starts (one platform bug on our side, one virtual machine with a flapping public IP that we eventually deleted rather than debugged), while DigitalOcean was the smoothest onboarding of the three.
HTTP latency: everyone passes, nobody ties
The k6 phase drives an in-cluster target service through four scenarios (smoke, a 50 rps baseline, a ramp to 300 rps, a spike to 400 rps) using open-model executors, with SLO thresholds on every scenario. The headline is boring in the best way: all three clusters passed all eight thresholds with a 0.00% error rate and zero dropped iterations. These are healthy clusters. The texture is in the percentiles:
| p95 / p99 in ms | UpCloud | OVH | DigitalOcean |
|---|---|---|---|
| smoke (unloaded) | 0.21 / 0.50 | 0.40 / 0.77 | 2.36 / 6.38 |
| baseline, 50 rps | 0.94 / 1.02 | 0.64 / 0.73 | 3.10 / 9.84 |
| stress, ramp to 300 rps | 0.81 / 0.93 | 0.52 / 0.61 | 3.69 / 10.86 |
| spike, 400 rps | 0.72 / 0.92 | 0.46 / 0.56 | 5.60 / 13.74 |
UpCloud and OVH live in the same sub-millisecond world, with OVH’s cpu-optimized line taking the loaded scenarios and UpCloud the unloaded one. DigitalOcean is playing a different sport: an order of magnitude slower at every percentile, a p99 that climbs with load to 13.7 ms, and individual requests spiking to 75 to 206 ms. Nothing about that breaches an SLO built for real applications, and for a stateless web tier it may be perfectly acceptable. But the shape of the curve, tails that grow as load grows, is the signature of CPU steal on a shared tier, and it is the first appearance of a pattern the rest of the suite keeps confirming.
Network: the ceiling has two different causes
The iperf3 phase forces client and server onto different nodes and measures pod-to-pod TCP through the overlay, thirty seconds in each direction with four streams.
| UpCloud | OVH | DigitalOcean | |
|---|---|---|---|
| Forward | 5.40 Gbit/s | 3.61 Gbit/s | 1.76 Gbit/s |
| Reverse | 5.44 Gbit/s | 3.35 Gbit/s | 1.62 Gbit/s |
| What limits it | receiver CPU at ~95% | receiver CPU at ~140% | provider cap, CPU at ~55% |
The ranking matters less than the last row. On UpCloud and OVH the bottleneck is the 2-vCPU receiver saturating on packet processing, which means the number scales with instance size: buy bigger nodes and the pipe widens. On DigitalOcean the CPU was loafing at 55% while throughput sat at 1.7 Gbit/s, which is a provider-side cap: no instance resize changes it. Same benchmark, opposite capacity-planning advice, and you cannot tell the two ceilings apart from a spec sheet.
Storage: three philosophies, one hard threshold
Disk is where the three providers stop being comparable products at all. UpCloud sells QoS-capped network block storage: sequential transfer pinned at a flat 410 to 420 MB/s on both workers, but 100k random read IOPS, identical to within one IOPS across nodes, and durable independently of the VM. OVH’s local NVMe posts spectacular sequential numbers (up to 7.5 GB/s reads) and the best fsync latency of the test, but caps at 20k IOPS and is instance-local, so its durability story is your replication story. DigitalOcean’s local SSD landed in between on throughput and below both on consistency.
The measurement with the most operational teeth is fsync latency, the write pattern of etcd’s log and of any database that means it when it commits. etcd’s guidance wants the 99th percentile of fdatasync under 10 ms:
OVH wins this outright at 0.4 ms, an order of magnitude inside the bar. UpCloud sits comfortably around 1 ms. DigitalOcean’s two workers came in at 4.23 and 11.86 ms, and that second number is the single most important measurement of the whole exercise: a node that would quietly degrade an etcd member or a PostgreSQL primary, on a cluster that looked immaculate from the outside. The full per-worker table:
| Per worker | UC w0 | UC w1 | OVH w0 | OVH w1 | DO w0 | DO w1 |
|---|---|---|---|---|---|---|
| Seq write MB/s | 412 | 411 | 4,632 | 3,167 | 144 | 952 |
| Seq read MB/s | 420 | 420 | 6,800 | 7,492 | 1,953 | 4,654 |
| 4k rand read IOPS | 100,120 | 100,119 | 20,066 | 20,060 | 27,693 | 48,866 |
| 4k rand write IOPS | 46,647 | 46,965 | 20,062 | 20,061 | 22,741 | 34,155 |
| fsync ops/s | 4,372 | 4,428 | 7,350 | 7,072 | 383 | 889 |
| fdatasync p99 ms | 0.93 | 1.09 | 0.39 | 0.43 | 11.86 | 4.23 |
Read the OVH and UpCloud columns twice: 20,066 next to 20,060, and 100,120 next to 100,119. That is what a QoS cap looks like, and it is a feature. A cap you hit every time is a number you can plan around.
The twin-node lottery
Every pair of workers in this test was the same instance type, in the same region, provisioned in the same hour. Here is what sysbench measured on their CPUs:
UpCloud’s pair differs by half a percent, OVH’s by one percent. DigitalOcean’s differs by 2.8x, and the same pair was 6.6x apart on sequential writes and 2.3x apart on fsync throughput. That is not measurement noise; it is shared-tier contention, two lottery tickets that came up very differently. The subtle consequence for Kubernetes is that the scheduler has no idea: it sees two identical nodes with identical allocatable CPU and will happily place your latency-critical pod on the slow one. UpCloud was not spotless either (its workers differed 2.4x on memory bandwidth, 11.3 vs 4.6 GB/s), which is a good reminder that node-level variance is a spectrum every provider sits on somewhere, not a defect unique to one.
What to place where
Ranking the three misses the point unless you say what for. On this day, at this size class:
UpCloud was the best all-rounder and our pick for a production control plane and stateful workloads: 5x the random-IOPS headroom of OVH’s cap and double DigitalOcean’s best worker, the fastest network with CPU as the only ceiling, the strongest CPU results, and storage that survives the VM.
OVH won everything latency-shaped: the best fdatasync p99 by a wide margin, the best loaded HTTP percentiles, and near-perfect node consistency, with the 20k IOPS cap, slower network and local-disk durability model as the trade.
DigitalOcean’s entry tier had the cleanest provisioning experience of the three and passed every SLO we set, but shared vCPU makes it the wrong home for anything tail-sensitive: the only etcd-bar breach in the test, a provider-capped network, and twin nodes 2.8x apart. On its dedicated tier this could be a different comparison; we did not test it.
The honest summary is that “which provider is fastest” turned out to be the least interesting question. The interesting ones are: is the ceiling CPU or a cap, is the storage capped-and-consistent or fast-and-variable, and do twin nodes actually perform like twins? Those answers move real money: at these prices you are choosing between 100k consistent IOPS, 0.4 ms fsyncs, or a smoother onboarding, and only a measurement tells you which one you are buying.
Run it against your own clusters
Every number above came from three Kubernetes Jobs and a target deployment, about fifteen minutes per cluster, with pass/fail SLO verdicts rather than eyeballed graphs. The suite, its design decisions and its sharp edges are covered in the methodology post. The loop that made the comparison cheap was Ankra’s: declare the same cluster shape on three providers, deploy the benchmark workloads as a stack on each, run the phases, keep the logs, delete the clusters. Total spend for the answer: a few euros. The next time a pricing page and a benchmark disagree, believe the benchmark, especially if it is yours.
Get started: Create a free account on Ankra.
Join our community: Slack
Follow us on: LinkedIn | GitHub
Contact us: [email protected]
Get the next post in your inbox
Related Posts
AWS vs GCP vs Hetzner: Six Clouds, One Kubernetes Benchmark
We ran the same k6, iperf3 and fio suite on EKS, GKE and Hetzner and lined the results up against UpCloud, OVH and DigitalOcean. The cheapest node in the test won more than it had any right to.
The Spec Sheet Is Not a Benchmark: Testing Kubernetes Clusters in 15 Minutes
Two identical cloud VMs measured 2.8x apart on CPU; one failed etcd's fsync bar. A 15-minute in-cluster benchmark suite catches this before production does.