Cloudflare R2 vs S3
22 Apr 2026S3’s egress fees are the actual cost of using it at any real scale — storage itself is cheap on both. R2 charges nothing to read data out, and that one difference decides this comparison for most self-hosted and small-team setups.
The core difference: egress
S3 charges per GB every time data leaves AWS to the public internet. Serve images, video, or downloadable files directly from a bucket and that egress line is often bigger than the storage line by the time you’re serving real traffic.
R2 charges zero egress. You pay for storage and for requests, not for bandwidth out. Cloudflare can do this because R2 is designed to sit behind Cloudflare’s CDN and network, where they’re not paying a third party for that bandwidth the way a smaller provider would.
| S3 | R2 | |
|---|---|---|
| Egress to internet | Charged per GB | Free |
| Storage cost | Comparable | Comparable |
| API | S3-compatible (native) | S3-compatible (compatible layer) |
| Ecosystem/tooling | Broadest — everything supports S3 | Works with most S3 SDKs and tools |
| Multi-region replication | Native, mature | More limited |
| Free tier | None ongoing | 10GB storage, 1M Class A + 10M Class B ops/month |
When R2 clearly wins
Serving public assets at any real volume. Images, video, downloadable builds, static site assets — anything where a person or a CDN pulls data out repeatedly. This is the exact case S3’s egress pricing is worst at and R2’s zero-egress model is built for.
Small projects and side products. R2’s free tier covers a genuinely useful amount of storage and requests before you pay anything, which matters when you’re not yet sure a project has traffic worth paying for.
You’re already on Cloudflare. If your DNS, CDN, and possibly Workers are already there, R2 is one dashboard, one bill, and pairs natively with Cloudflare’s cache in front of it — a public bucket through Cloudflare’s CDN, cached at the edge, costs you nothing extra to serve repeatedly.
When S3 still wins
Deep AWS integration. Lambda triggers on object creation, Athena querying directly against bucket data, Glacier tiers for cold archival, IAM policies scoped to the object level — this tooling is mature on S3 and thinner or absent on R2. If your pipeline already lives in AWS, moving just the storage layer out adds cross-cloud egress cost on the AWS side to compensate for what you saved on R2’s side.
Compliance requirements naming AWS specifically. Some enterprise contracts and compliance frameworks specify AWS by name. Not a technical limitation, but a real blocker if it applies to you.
You need S3’s full feature surface. Object Lock, S3 Select, deep versioning and lifecycle policy options — R2 covers the common 80% of the S3 API, not all of it. Check the specific feature you rely on before assuming parity.
Migration is close to a non-event
R2’s API is S3-compatible, so most tooling (aws-sdk, boto3, rclone) points at it by changing the endpoint:
aws s3 cp ./backup.tar.gz s3://my-bucket/ \
--endpoint-url https://<account-id>.r2.cloudflarestorage.com
rclone handles a full bucket sync the same way, with an R2 remote configured against the S3-compatible endpoint. You’re not rewriting application code, just repointing the endpoint and swapping credentials.
The actual decision
Calculate your current S3 egress line specifically, not total storage cost — that’s the number that either justifies the move or doesn’t. If you’re serving public files at any real volume and not deeply wired into AWS-specific services, R2 is very likely cheaper with no meaningful functionality loss. If you’re leaning on Lambda triggers, Athena, or Glacier, the egress savings on R2 probably don’t outweigh rebuilding that part of the pipeline.