I have a bad habit of treating object storage like a solved problem. You sign up for whatever your platform suggests, point the AWS SDK at a different endpoint, and never think about it again — until the bill arrives, or until a user in Singapore complains that uploads take four seconds. Over the last couple of months I moved three real workloads — a static asset bucket for a docs site, a user-upload pipeline for a small SaaS, and a nightly database backup job — across Cloudflare R2, Backblaze B2, and Tigris, and the differences turned out to be sharper than the marketing pages suggest.
The common ground is real: all three are S3-compatible, so in practice you change an endpoint URL, a region string, and a pair of credentials, and your existing code keeps working. That compatibility is the whole reason this comparison is even tractable. The interesting question is not "which API do I learn" — it is the same API — but "where does my data physically live, what does it cost to read it back, and how much of the surrounding plumbing do I have to build myself." Those three questions sort the field cleanly.
The egress problem is the whole game
If you have ever been surprised by an AWS S3 bill, it was almost certainly egress. Storing a terabyte on S3 is cheap; serving that terabyte to the internet repeatedly is where the meter spins. AWS charges roughly nine cents per gigabyte of data transferred out to the internet, and for a content-heavy app that line item can dwarf the storage cost by an order of magnitude. Every alternative in this comparison exists, in part, as a reaction to that pricing.
Cloudflare R2's headline pitch is the bluntest: zero egress fees. You pay for storage (in the rough neighborhood of $0.015 per GB-month as of mid-2026) and for operations — Class A writes and Class B reads, priced per million — but bandwidth out is free, full stop. For anything read-heavy, this changes the arithmetic entirely. A bucket that serves a viral asset, a popular dataset, or a media library does not punish you for being popular. There is a free tier as well, generous enough that small projects can run on R2 for nothing for a long time.
Backblaze B2 attacks the same problem from the storage side. Its at-rest price is the lowest of the three — around $0.006 per GB-month, give or take, which is roughly a third of S3 and noticeably under R2. B2 does charge for egress, but with two important escape hatches: a daily free-egress allowance scaled to how much you store, and, crucially, free egress when you serve through Cloudflare via the long-standing Bandwidth Alliance. Put a Cloudflare CDN in front of a B2 bucket and your read path can be effectively free while you pay the cheapest storage rate on the market.
Tigris does not try to win the price race. Its egress is competitively priced rather than free, and storage sits in the same general band as R2. What you are paying for instead is placement — your objects are replicated across regions automatically and served from close to the requester. That is a different value proposition, and it only makes sense once you stop thinking about the bill and start thinking about latency.
Backblaze's free egress through Cloudflare only applies to traffic that actually flows through Cloudflare's network — typically a CDN or Worker sitting in front of the bucket. If you hit the B2 S3 endpoint directly from your servers, or from a client that bypasses the CDN, you pay normal egress past the daily free allowance. I have watched a "free" architecture quietly start charging because a background job was pulling objects straight from the origin. Confirm the path, not just the diagram.
Latency and where your bytes actually sit
S3 compatibility tells you nothing about geography, and geography is where Tigris makes its case. R2 and B2 both store your data in a region (or, for R2, a location hint you can nudge), and Cloudflare's network does the work of caching and serving close to users via its CDN. That CDN layer is excellent — for cacheable content, R2-behind-Cloudflare is genuinely fast almost everywhere. But the origin still lives somewhere specific, and a cache miss, or a write, or an uncacheable object, pays the round-trip to that origin.
Tigris is built differently. It is, at heart, a globally distributed store layered on FoundationDB, designed so that objects are replicated to multiple regions and the first byte comes from a nearby one without you orchestrating any of it. For workloads where reads are not cacheable — think per-user data, signed-URL access to private objects, or anything dynamic — that automatic regional replication is the headline feature. When I tested the user-upload pipeline, the difference between "single-region origin with a CDN in front" and "natively multi-region" showed up most clearly on first writes and on cold private reads from far away. On a static, cacheable docs bucket, honestly, I could barely tell the three apart once a CDN was in the path — the cache absorbs the difference.
The mental model I landed on: R2 and B2 are single-origin stores that you make fast with caching, and Tigris is a multi-origin store that is fast by construction. If your content is public and cacheable, caching wins and the cheaper option is fine. If your content is private, dynamic, or write-heavy and your users are spread across continents, native replication earns its keep.
Because all three speak the S3 API, it is tempting to think they are interchangeable behind a config flag. They mostly are — but the edges differ. Multipart upload behavior, conditional-request and ETag semantics, lifecycle rules, event notifications, and presigned-URL quirks are exactly where 'S3-compatible' becomes 'S3-compatible-ish.' Before you commit, run your actual upload and lifecycle code against the candidate, not just a hello-world PutObject. The 95% that works is easy; the 5% that does not is what eats your weekend.
Integration: how much plumbing comes in the box
This is where the three diverge most, and where your existing platform probably decides the answer for you.
R2 is a Cloudflare product, so its best trick is the tight binding to Workers. You can attach a bucket to a Worker as a native binding and read and write objects without an HTTP round-trip or even credentials in the usual sense — the storage is just there, in the same runtime, at the edge. If you are already building on Workers and Pages, R2 stops feeling like external storage and starts feeling like a local resource. It also exposes the standard S3 API for everything else, so tools like rclone, the AWS CLI, and S3 SDKs all work.
Tigris is woven into the Fly.io ecosystem the same way R2 is woven into Cloudflare's. If you deploy apps on Fly, provisioning a Tigris bucket is a first-class command, credentials flow in as secrets, and the multi-region storage naturally complements Fly's multi-region compute. It is available outside Fly too — it is a standalone S3-compatible service you can point anything at — but the smoothest experience, the one the product is clearly designed around, is the Fly developer running fly storage create and getting on with their day.
Backblaze B2 is the platform-agnostic one. It has no compute platform of its own to bind to, which is both its weakness and its honesty: it is pure storage. The integration story is "it speaks S3, and it pairs with Cloudflare for free egress." For backups, archives, and any setup where you want storage decoupled from your application platform, that neutrality is a feature. B2 has been doing exactly this for years and the reliability reputation reflects it.
How they stack up side by side
A few honest caveats on that table. The prices are approximate and move; treat them as the shape of the market rather than a quote, and check the current pricing pages before you model a budget. "Free egress" on R2 is genuinely free for internet transfer, but operations (especially Class A writes) are not, so a write-heavy or many-small-objects workload can still run up a bill in a column you were not watching. And B2's "free via Cloudflare" depends on the traffic actually traversing Cloudflare, as the warning above labors.
Who should pick which
Static assets and public media (docs sites, images, downloads): R2 is the path of least resistance. Free egress plus Cloudflare's CDN means a popular asset costs you essentially storage-only, and if you are on Pages it is one binding away. If you are storing genuinely large libraries and watching every cent, B2 behind Cloudflare gets you the cheapest storage with the same free read path — at the cost of wiring up the CDN yourself.
User uploads in a multi-region app: This is Tigris's sweet spot. Uploads are writes, profile reads are often private and uncacheable, and your users are everywhere. Native replication means the person in Sydney is not waiting on a us-east origin. If your users are concentrated in one region, the advantage shrinks and R2 is simpler.
Backups and archives: B2, almost reflexively. You are optimizing for the lowest at-rest price and you read the data rarely, so egress barely matters and storage cost dominates. Decoupling backups from your app platform is also good hygiene — you do not want your backup target to disappear with your compute vendor.
Edge apps on a specific platform: Let the platform decide. On Cloudflare, R2. On Fly.io, Tigris. The native binding or first-class CLI is worth more in daily ergonomics than a marginal price difference, and both expose the S3 API for the cases the native path does not cover.
If you take one thing away: figure out your read pattern first. Read-heavy and public points at R2. Read-rarely points at B2. Read-dynamic-and-global points at Tigris. The S3 API means you can prototype against all three in an afternoon and switch later if you are wrong — which is the real luxury here.
FAQ
Originally published at pickuma.com. Subscribe to the RSS or follow @pickuma.bsky.social for new reviews.
Top comments (0)