At 21:24 UTC on July 20, 2026, requests to Cloudflare R2 in the ENAM region started coming back with 500 errors. They kept coming back that way for the next 17 hours and 15 minutes, until Cloudflare's status page finally reported the issue resolved at 14:39 UTC on July 21. Two days later, on July 22, R2 broke again in the same region for another two hours and nine minutes. Any app whose images, build artifacts, or user uploads live in R2 was, for a meaningful chunk of that week, serving errors — not because of anything in its own code, but because of an object-storage bucket it doesn't operate and can't see inside of.
That's a rough week for a service a growing number of self-hosting cost comparisons hold up as the benchmark to beat: "zero egress fees" is the line every self-hosted-versus-R2 pitch has to answer. This post does the two things that pitch actually owes a reader — what the outage really cost in availability terms against R2's own SLA, and a worked comparison of what the same workload costs on Hetzner-owned infrastructure instead, including the point where R2's free egress stops being the better deal.
Three Incidents, Two Regions, One Month
The timeline, pieced together from Cloudflare's own status updates:
| Incident | Region | Start (UTC) | End (UTC) | Duration |
|---|---|---|---|---|
| R2 elevated 500 errors | WEUR | Jul 14, 15:38 | Jul 14, 17:36 | 1h 58m |
| R2 500 errors | ENAM | Jul 20, 21:24 | Jul 21, 14:39 | 17h 15m |
| R2 elevated errors | ENAM | Jul 22, 08:22 | Jul 22, 10:31 | 2h 09m |
| Total (July 2026) | 21h 22m |
Cloudflare hasn't published a public post-mortem naming the root cause of any of the three incidents as of this writing — the status updates describe the symptom (elevated or 500 errors) and the resolution, not the underlying failure. That's normal for R2's incident communication, but it also means a tenant reading the status page learns exactly as much as everyone else watching the same page: nothing about why, only when it stopped. And it means the July 20-21 incident wasn't a one-off — it's the longest entry in a month that already had two other R2 error windows on either side of it, across two different regions.
What 99.9% Actually Promises
R2's SLA guarantees 99.9% monthly uptime. If Cloudflare misses that, the remedy is a service credit against future R2 charges, sized as a percentage of the fees paid in the preceding 30 days — not compensation for downstream impact, and not owed automatically; the tenant has to file the claim.
Run the math on what 99.9% actually permits: a 30-day month has 43,200 minutes, so a 99.9% target allows roughly 43 minutes of downtime before the SLA is even breached. A tenant with buckets only in ENAM already blew that budget by ~27x from two incidents alone (17h 15m plus 2h 09m); one with buckets in both ENAM and WEUR saw all three windows, 30x over. Either way, the payout for a breach that size is a credit against next month's R2 bill, which on a modest workload might be worth single-digit dollars. It says nothing about the 500s a tenant's own customers saw, or the support tickets that outage generated on the tenant's side, not Cloudflare's.
That asymmetry is the actual shape of the trade a shared multi-tenant service offers: a low sticker price, and a remedy scoped to that same sticker price when it fails — not to what the failure cost you.
The Zero-Egress Pitch, Priced Against Owning the Disk
Here's where "zero egress" earns its keep or doesn't. Take a representative tenant workload: 4TB of stored objects (user uploads, build artifacts, media), 5 million write operations a month, and objects averaging 500KB — a fairly ordinary media-heavy app, not an extreme case in either direction.
Cloudflare R2 (Standard storage, per Cloudflare's published pricing): $0.015/GB-month storage, $4.50/million Class A operations (writes), $0.36/million Class B operations (reads), egress free.
Hetzner Object Storage (per Hetzner's published rates, corroborated across multiple independent trackers): $5.99/month base price including 1TB storage and 1TB egress, then $5.95/TB-month for additional storage and $1.20/TB for additional egress — no per-request billing at all.
At 8TB of monthly egress (2x the stored volume turning over once every two weeks — a believable rate for an active app):
| Monthly egress | Cloudflare R2 | Hetzner Object Storage |
|---|---|---|
| 8TB | $60 storage + $22.50 writes + $5.90 reads = $88.40 | $5.99 base + $17.85 storage overage + $8.40 egress overage = $32.24 |
| 20TB | $60 + $22.50 + $14.76 = $97.26 | $5.99 + $17.85 + $22.80 = $46.64 |
| 50TB | $60 + $22.50 + $36.86 = $119.36 | $5.99 + $17.85 + $58.80 = $82.64 |
| 100TB | $60 + $22.50 + $73.73 = $156.23 | $5.99 + $17.85 + $118.80 = $142.64 |
The Hetzner side keeps its edge past even a 25x-per-month egress turnover, because R2's "free" egress is only free in the literal bandwidth sense — the read volume still shows up as billed Class B operations, and at 500KB average object size, a terabyte of egress is roughly 2 million requests. Working the two cost curves out to where they cross puts the breakeven around ~130TB of monthly egress on 4TB stored — a turnover rate over 30x, well into genuinely viral or CDN-scale traffic, not the range a typical tenant workload occupies. Below that line, egress being "free" doesn't outrun a linear-but-cheap $1.20/TB overage charge with no per-request meter attached to it at all.
And that's still Hetzner's own managed Object Storage product — the same $5.99/month base price and per-TB overage a hosted-PaaS customer pays. A Cluster-API fleet running Garage or Ceph RGW itself, on local disk already attached to nodes the fleet is provisioning anyway, skips that markup entirely. Hetzner's own bulk-storage reference point (its Storage Box product, priced around €3.20/~$3.50 per TB-month with unlimited traffic) puts a floor on what raw disk costs before any managed-service margin is added: 4TB in that ballpark runs roughly $14/month, no egress meter and no per-request billing, because the disk is already part of hardware the fleet's Cluster-API budget is paying for regardless. The tradeoff for that price is operational, not financial: replication, disk failure handling, and capacity planning become the platform operator's job instead of a line item on someone else's invoice.
Why the Same Trade Cuts Both Ways
R2's pricing and R2's incident window come from the same place: a single shared multi-tenant edge network, priced low because the fixed cost of running it is spread across every tenant on it. That's also exactly why one region having a bad week takes every ENAM tenant's bucket down with it at once — the same architecture that subsidizes the price is the one that makes the blast radius wide.
Self-hosting an S3-compatible layer — Garage or Ceph RGW on a Cluster-API-managed Hetzner fleet — doesn't make outages disappear; disks fail and software has bugs regardless of who owns the hardware. What changes is who's in the incident queue. A bug in your own Garage cluster gets your own operator's attention first, on your own timeline, instead of queuing behind however many other tenants Cloudflare is triaging in the same region at the same time. There's no status page to refresh and no SLA-credit claim form to file, because there's no vendor relationship standing between the failure and the fix.
When R2's Edge Still Wins
None of this makes R2 a bad product — it makes it the wrong tool for a specific job. Cloudflare's global anycast network puts objects a few milliseconds from users on nearly every continent, a capability a single-region owned Hetzner fleet doesn't have without deliberately building out multi-region replication of its own. For latency-sensitive, geographically distributed, non-critical static assets — marketing site images, public CDN-fronted content where a few minutes of 500s degrades UX but doesn't take a product down — R2's edge is a real advantage self-hosting has to work to replicate, and often isn't worth replicating.
The workloads where owning the disk pays off are the ones this post's numbers describe: bandwidth-heavy tenant data below the ~130TB/month breakeven, and anything where an hours-long outage in someone else's region is unacceptable regardless of the SLA credit attached to it. Object storage, like the rest of a self-hosted platform's stack, doesn't have to be all-in on one model — it's a per-workload decision, not a manifesto.
Bex.co is the open-source, AI-native Render alternative built on Cluster API — push a git repo, get a running HTTPS service on machines you own. Object storage is one more layer a fleet can run on its own owned Hetzner hardware instead of renting from someone else's shared edge. Star the repo on GitHub or deploy your first app today.



