R2 Has No Egress Fees, But Class A Operations Are Real
R2’s pitch is the egress, and the egress genuinely is zero. Storage runs $0.015 per GB-month. What people skip is the operations table:
- Class A, which mutates or lists: $4.50 per million
- Class B, which reads: $0.36 per million
The free tier is generous enough that most projects never look further. Ten gigabytes stored, a million Class A and ten million Class B every month. A personal site will not escape that.
A backup job might, and the reason is never the number of objects.
Multipart uploads multiply
Uploading one large object isn’t one operation. CreateMultipartUpload is Class A, CompleteMultipartUpload is Class A, and every single UploadPart in between is Class A too.
A 5 GB file in 5 MB parts is a thousand parts, so roughly a thousand and two Class A operations for one object. Same file in 100 MB parts is fifty-two.
Most SDKs default to small parts, which is the right call over a flaky connection. Uploading from a server with a good one, raising the part size is the single biggest change you can make.
Listing is a write-priced operation
ListObjects is Class A, and it pages a thousand keys at a time. Walking a bucket of a million objects costs a thousand operations per pass.
That’s fine once. It’s not fine as the first step of a sync that runs hourly, which is how rclone and aws s3 sync style tools work by default: list everything, compare, then upload the difference. The comparison is free and the listing isn’t.
If you need to know what’s in a bucket, keep your own index in D1 or KV and update it on write. Listing to answer questions the database should answer is the pattern that costs money.
Still cheaper
None of this makes R2 expensive. A workload serving real traffic will save far more on egress than it spends on operations, and that’s the whole point of the product.
Just read “no egress fees” as one line of the bill rather than the whole bill.
