Skip to main content

Zero Egress, Metered State: What Cloudflare's January 2026 Durable Objects Bill Actually Costs Against a Flat Hetzner Box

15 min readDora NodaDora Noda
Share

Cloudflare's best marketing line is three words long — zero egress fees — and it is completely true. Put a gigabyte in R2, serve it a million times through the CDN, and the bandwidth line on your invoice really is $0. That single decision has saved media-heavy teams thousands a month versus S3's $0.09/GB, and Cloudflare has never walked it back.

A stateful app does not fail on bandwidth. It fails on rows and gigabytes that never leave the edge — and that is exactly where Cloudflare started a new meter on January 7, 2026, with a second one arriving no earlier than August 10, 2026. If you are pricing a Postgres-backed service on Workers + Durable Objects + Workflows against a Postgres sitting on an owned Hetzner box, the egress line is not the one that moves. The storage line is.

This post puts a number on it.


The price table you actually need is not the egress table

Here is the meter that now applies to every SQLite-backed Durable Object, from the Durable Objects pricing page (last updated August 10, 2026):

DimensionWorkers FreeWorkers Paid ($5/mo base)
Rows read5M / day (fail if exceeded)First 25B / month included, then $0.001 / million rows
Rows written100K / dayFirst 50M / month included, then $1.00 / million rows
SQL stored data5 GB total (fail if exceeded)First 5 GB-month included, then $0.20 / GB-month
Requests100K / day1M / month included, then $0.15 / million
Duration13,000 GB-s / day400,000 GB-s / month included, then $12.50 / million GB-s

Two footnotes that double your bill if you miss them: every put()/get() through the KV-compatible API is billed as hidden SQLite rows, and every index you add makes every write count again — "every row update of an index counts as an additional row written." That Medium deep-dive from February 2026 was blunt about it: at $1.00/M writes vs $0.001/M reads, writes are 1,000× more expensive than reads.

Workflows adds a second meter on top, from the Workflows pricing page:

DimensionWorkers FreeWorkers Paid
Steps3,000 / dayFirst 500K / month included, then $0.80 / 100K steps
Storage (GB-month)1 GBFirst 1 GB-month included, then $0.20 / GB-month
CPU + RequestsStandard Workers free limitsStandard Workers paid ($0.30/M requests, $0.02/M CPU-ms) — idle/waiting time is not billed

Billing for SQLite-backed DO storage was enabled January 7, 2026; Workflows steps + storage billing starts no earlier than August 10, 2026. Both publish the rate today and only charge after the date — which is why teams that prototyped in 2025 saw $0 and assumed it would stay $0.

The question is not whether the rates are fair — they match D1 pricing exactly and are cheaper than most alternatives per unit. The question is when a real app crosses the included quota and what the overage totals to.


What "zero egress" covers — and what it never did

Zero egress at Cloudflare has always meant three things:

  1. R2 object storage — $0.015/GB-month stored, $0 egress to the internet. Compared to S3, that is the headline saving. AWS charges $0.09/GB out of us-east-1; R2 charges nothing.
  2. Workers + CDN bandwidth — no per-GB charge for responses served through Cloudflare's network. Cloudflare Queues also advertises "no data transfer (egress) or throughput charges."
  3. No hidden inter-region bandwidth bill between Workers and R2/D1 inside Cloudflare's fabric.

What it never covered, because it could not:

  • Durable Objects storage. A Durable Object is a strongly-consistent, single-threaded actor pinned to one location with its own SQLite database. You pay to read, write, and keep that database — egress is irrelevant because the data never leaves the object except via a request you already pay for.
  • D1 (Cloudflare's serverless SQLite). Same row pricing as DO SQLite, same logic.
  • Workflows persisted state. A Workflow that sleeps for a week waiting for a webhook bills $0 for CPU while sleeping but still accrues stored-state GB-months the whole time.
  • KV, Queues, R2 operations. R2 has no egress fee but does bill Class A/B operations ($4.50/M writes, $0.36/M reads) and storage.

In other words: Cloudflare zeroed the cost of moving bytes and kept the cost of keeping bytes. A stateless edge function that fetches from R2 and returns is almost free. A collaborative editor, a queue-backed job runner, or a per-user Durable Object that writes on every keystroke is not.

That distinction was free to ignore while DO SQLite storage was in beta with no bill. It is not free anymore.


The January bill, worked for three real workloads

To make this concrete, here are three workload shapes you would actually recognize — not a synthetic 1-row benchmark — each costed against the Paid plan (Free would simply error past its daily cap).

Small — chat sidecar for a 5-person team

  • 20M rows read, 10M rows written, 2 GB stored, ~80K DO requests, 50K GB-s duration, 100K Workflow steps
  • Reads are chat history loads; writes are messages + two indexes (so 10M logical writes ≈ 30M billed row-writes if you count index amplification — we show both)

Medium — multi-tenant SaaS (the "real startup" tier)

  • 5B rows read, 100M rows written, 10 GB stored, 5M DO requests, 300K GB-s, 1M Workflow steps

Heavy — event pipeline / collaborative canvas

  • 20B rows read, 500M rows written, 50 GB stored, 20M DO requests, 2M GB-s, 5M Workflow steps
WorkloadRows readRows written (billed)StoredStepsDO storage overageWorkflows overageDO requests + duration overageTotal Cloudflare state surcharge
Small20M10M (30M with 2 indexes)2 GB100K$0 — within 25B/50M/5GB$0 — within 500K/1GB$0 — within 1M req / 400K GB-s$0 ($0 with indexes)
Medium5B100M10 GB1M(100M − 50M) × $1/M = $50 + (10 − 5) GB × $0.20 = $1$51(1M − 500K)/100K × $0.80 = $4 + (10 − 1) GB × $0.20 = $1.80$5.80(5M − 1M) × $0.15/M = $0.60 + $0 duration (300K < 400K)~$57.40
Heavy20B500M50 GB5M(500M − 50M) × $1/M = $450 + (50 − 5) × $0.20 = $9$459(5M − 500K)/100K × $0.80 = $36 + (50 − 1) × $0.20 = $9.80$45.80(20M − 1M) × $0.15/M = $2.85 + (2M − 400K)/1M × $12.50 = $20$22.85~$527.65

Add the $5/month Paid plan floor to each and you get roughly $5, $62, and $533 just for the stateful layer — before the Workers invocation that called the DOs, before R2 or D1, before any egress you never paid anyway.

Two sensitivities worth calling out because they flip these numbers:

  • Reads are almost free until they are not. Even the Heavy workload's 20B reads is still within the 25B included. You have to cross 25B reads before the $0.001/M line matters at all. The bill lives and dies on writes.
  • Indexes are the hidden multiplier. The Small workload shows zero overage either way, but that 3× write amplification from two indexes is invisible until you add a third. At Medium, a 2-index table turns $50 of write overage into $150; at Heavy, $450 becomes ~$1,350. Cloudflare documents the rule in one line; most ORMs add the indexes without surfacing the cost.
  • Stored data is linear and patient. $0.20/GB-month looks trivial next to $1/M writes, but unlike writes it accrues whether the app is busy or idle. A Workflow that retains 30 days of completed-instance history on the Paid plan (the default) holds 3× the GB-months of a team that sets a 10-day retention. One retention parameter change can drop a $10 storage line to $3 without touching any business logic.

Why Workflows' "waiting is free" still has a storage tax

Workflows are priced like Workers with two additions — steps and storage — and the pitch is that idle time is genuinely free. A Workflow that calls step.sleep("wait for approval", "7 days") consumes no CPU for seven days. That is a real improvement over polling.

But the Workflow's persisted state — its event history, step inputs/outputs, and any intermediate data you stashed — is billed as GB-months for as long as the instance exists. The defaults matter here:

  • Free plan: instance state retained 3 days, 1 GB included.
  • Paid plan: instance state retained 30 days, 1 GB included, then $0.20/GB-month.

A concrete example: a workflow that processes a 2 MB payload (say, a signed image + JSON metadata) and keeps 1,000 concurrent or recently-completed instances. That is 2 GB of live state before any overhead. On the Paid plan you are billed (2 GB − 1 GB included) × $0.20 = $0.20/month if you delete promptly, but if you leave the 30-day default on 10,000 historical instances that is 20 GB × $0.20 = $4 for data that is already done. Teams that set retention to 7 days or explicitly delete via the API/CLI cut that line by 75% — and the changelog for August 2026 calls out retention as the intended lever.

The steps dimension is the other place where granularity bites. At $0.80 per 100K steps beyond the 500K included, a workflow you designed as "one step per API call × 27 regions × 9 phases ≈ 243 steps per run" (a real pricing rebuild cited in the Workflows changelog discussion) burns 243K steps per 1,000 runs. Three daily rebuilds for a month and you are at ~22M steps — well past included and adding ~$170 just in steps before CPU or storage. The per-step price is small; the step count is not, once you parallelize.

None of this is a gotcha — it is published, with a start date and a free tier. The risk is assuming "zero egress" means "zero marginal cost for state," and therefore not budgeting for steps or GB-months at all.


The recompute against a flat Hetzner box

The self-hosted counter-argument is not that Cloudflare is expensive — for stateless edge and R2 media delivery it is often the cheapest option by far — but that a Postgres running on a flat-rate box does not meter rows or GB-months at all.

Take Hetzner's current catalog after its April 2026 adjustment:

  • CX22 — 2 shared vCPU, 4 GB RAM, 40 GB NVMe, 20 TB included traffic€3.79/month (~$4.10)
  • CAX11 (ARM) — 2 vCPU, 4 GB RAM, 40 GB NVMe, 20 TB traffic — €3.79/month, same price, linux/arm64 ready
  • CPX22 — 3 vCPU, 4 GB RAM, 80 GB NVMe — €5.39/month (the June 15, 2026 generation rename kept the 20 TB flat)

Bandwidth on these boxes is flat — 20 TB in EU (1 TB in US) at no extra per-GB charge. Block storage is €0.044/GB-month if you need it; otherwise the local NVMe is included. A Postgres on that box can do 100M or 500M row writes for the same $4.10; the only scaling lever is when you need a bigger box, not a bigger invoice line.

Here is the same three workloads, total cost of the stateful layer only, both sides stated honestly (Cloudflare includes its free tiers; Hetzner is the box + Postgres, no extra metering):

| Workload | Cloudflare (DO + Workflows + duration, after free tiers) | Hetzner CX22 + local Postgres | Delta (Cloudflare − Hetzner) | |---|---|---:|---:|---:| | Small (10M writes, 2 GB, 100K steps) | ~$5 (base plan only) | ~$4.10 (1× CX22) | +$0.90 — essentially tied | | Medium (100M writes, 10 GB, 1M steps) | ~$62 | ~$4.10 (still 1× CX22) — or ~$8.20 if you add a second box for HA | +$58 ($54 with HA) | | Heavy (500M writes, 50 GB, 5M steps) | ~$533 | ~$15.99 (CPX42 / CX33-class) or ~$8.20 (2× CX22 with external volume) | +$517 (+$525 on 2× CX22) |

To keep the comparison fair, a few things Cloudflare still wins on are excluded from both sides: R2 media egress ($0 either way, but Hetzner's 20 TB is also $0 — see the Hetzner vs Fly.io recompute for the bandwidth math), and global anycast latency (Cloudflare's edge is genuinely global; a single Hetzner region is not, unless you run the K8GB-style multi-cluster setup or front with Cloudflare CDN itself — which many self-hosted teams do).

What the table does show:

  • At Small, it does not matter. Both options are under $10. Pick the one with the better developer experience for your team.
  • At Medium, the meter appears. ~$57 of the $62 Cloudflare total is write overage past the 50M included. That is one content-heavy SaaS doing ~3M writes/day — 38 writes/second average, easily a collaborative app.
  • At Heavy, the model diverges. 500M writes/month is ~190 writes/second. Cloudflare bills every million rows past 50M; Hetzner bills the same box whether it does 10M or 500M. The delta is a full extra server fleet for a year, every month.

Sensitivity check: if that Medium workload adds two indexes and no one measures, its write overage triples from $50 to $150 and the total jumps to ~$162 — a 2.6× swing from a schema decision that never appeared on any pricing calculator. On Hetzner, the same indexes cost a few milliseconds of query planner time and zero dollars.


When each model wins — a decision framework

Choose Cloudflare's stateful stack when:

  • Your workload is read-heavy and globally distributed — 25B reads included means most apps never pay for reads, and edge placement is worth more than row cost.
  • Your state is small and ephemeral — a Durable Object per user that holds a few kilobytes of session and hibernates between requests can stay under 5 GB and sip duration via the Hibernation API (the docs' Example 4 shows a 100-DO chat dropping from $143 to $20/month purely by hibernating).
  • Your biggest cost on AWS was egress for media/assets — R2's zero egress alone can justify the platform, even if you keep Postgres elsewhere.

Choose flat Hetzner (or any owned NVMe) for state when:

  • Your workload is write-heavy — anything past ~50M row writes/month is paying $1/M forever, while a CX22 does not meter writes.
  • Your state is large or long-lived — 50 GB of tenant data is $9/month of storage overage on Cloudflare but $0 on a box that already has 40–80 GB NVMe.
  • You run Workflows at high step volume — the included 500K steps is generous for orchestration but tiny for "one step per row" fan-out patterns.
  • You want cost predictability — Hetzner's number next month is the same as this month's; Cloudflare's is a function of row counts you have not yet written.

A common hybrid that gets the best of both — and that Bex-style self-hosted PaaS makes straightforward — is Cloudflare in front, Postgres on owned hardware behind: use Workers + R2 + CDN for zero-egress delivery and edge auth, but keep the write-heavy database on a Hetzner fleet where the storage meter does not exist. You keep the marketing promise where it helps (bandwidth) and sidestep it where it does not (rows).


The pattern to watch

This is not a Cloudflare-specific story. It is a pattern for every maturing serverless platform in 2026:

  • Fly.io started billing volume snapshots in January 2026 and inter-region private networking in February — both previously bundled.
  • Vercel's bandwidth overage rose 33% to $0.15/GB on top of seat pricing.
  • Leapcell, Upsun, and Railway each added new metered dimensions rather than raising the base price.

Vendors do not raise the headline price. They find the previously-bundled cost that growth no longer covers and give it its own line item. Cloudflare did it with storage, which is honest and sustainable — free-at-first then metered is better than surprise. The team's job is simply to notice that the line item exists, price it for their actual write volume and index count, and decide whether that cost belongs on a meter or on a flat box they already pay for.

Zero egress is still a reason to use Cloudflare. It is no longer a reason to assume a stateful app on Cloudflare is free.

Bex.co is the open-source, AI-native Render alternative — push a git repo, get a running HTTPS service on machines you own. Bring your own Postgres (or run it on the same Cluster-API fleet) and front it with Cloudflare R2 for zero-egress assets — the database never needs a per-row meter. 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