Cloud & Infrastructure · Storage
Cloudflare R2 vs AWS S3 in 2026: The Storage Decision for Developer Teams
R2's zero-egress pricing looks compelling on paper. Here's when it actually saves money, when S3's ecosystem still wins, and how to migrate if you decide to switch.
Anurag Verma
6 min read
Sponsored
AWS S3 is the default for object storage because it’s been around since 2006, integrates with everything, and has a reliability record that’s hard to argue with. But the billing model has a problem: egress fees.
Every time an object leaves S3 to the internet, you pay roughly $0.09 per GB in US regions. That number doesn’t look alarming on a small app, but it compounds. A product that stores user-uploaded images and serves them directly from S3 can find egress costing more than storage within a few months of growth.
Cloudflare R2, launched in 2022, is built around a different pricing principle: no egress fees. You pay for storage and operations, but getting data out costs nothing. Whether that makes R2 the right choice for you depends on your usage pattern and how much of the rest of your stack you’re comfortable moving.
The Pricing Comparison
R2 and S3 pricing (approximate as of mid-2026 — check current rates before planning):
| Cost | Cloudflare R2 | AWS S3 Standard (us-east-1) |
|---|---|---|
| Storage | ~$0.015/GB/month | ~$0.023/GB/month |
| Egress to internet | $0 | ~$0.09/GB (first 10TB) |
| Class A operations (PUT, POST) | $4.50 per million | ~$5.00 per million |
| Class B operations (GET) | $0.36 per million | ~$0.40 per million |
| Free tier | 10GB storage, 1M reads, 1M writes/month | Limited |
Storage is cheaper with R2. Operations are comparable. The gap is entirely in egress.
The math works out strongly in R2’s favor for any workload where you’re serving content directly to users: media hosting, file downloads, image storage for web apps, user-generated content. If you have 500GB stored and serve 2TB per month to users:
- S3: ~$11.50 storage + ~$180 egress = ~$191.50/month
- R2: ~$7.50 storage + $0 egress = ~$7.50/month (plus operation costs, roughly $2-5)
The difference is real. At scale, egress is where cloud bills grow.
Where S3 Still Wins
R2 has caught up on a lot of features since 2022, but the ecosystem gap with S3 is still significant.
AWS Lambda integration. S3 events trigger Lambda functions natively. You can respond to object uploads, deletions, and modifications without polling. R2 doesn’t have a direct equivalent — Cloudflare Workers can respond to R2 events, but you’re in the Cloudflare ecosystem.
Storage classes and lifecycle rules. S3 has Intelligent-Tiering, Glacier, and lifecycle policies that automatically move objects to cheaper storage after set periods. For long-term archival or cold backups, S3 still has more options. R2 added lifecycle rules in 2024, but they’re less comprehensive.
Cross-region replication. S3 supports automated replication to other AWS regions. R2 is globally distributed by default (Cloudflare’s network handles the distribution), but multi-region replication as a configurable feature is different from S3’s approach.
AWS service integrations. Athena queries S3 directly. Redshift, EMR, SageMaker, and Glue all integrate with S3 natively. If you’re in the AWS ecosystem and using these services, R2 would require significant workflow changes.
Compliance tooling. S3 has more mature compliance features: Object Lock (WORM), access logging, AWS Config support, and direct integration with AWS CloudTrail. For teams in heavily regulated industries, this matters.
R2’s Specific Strengths
Beyond the egress pricing, R2 has some genuine advantages:
Cloudflare Workers integration. If you’re already using Workers, accessing R2 from a Worker is as simple as a binding:
// wrangler.toml
[[r2_buckets]]
binding = "BUCKET"
bucket_name = "my-bucket"
// worker.ts
export default {
async fetch(request: Request, env: Env) {
const url = new URL(request.url);
const key = url.pathname.slice(1);
const object = await env.BUCKET.get(key);
if (!object) return new Response("Not Found", { status: 404 });
return new Response(object.body, {
headers: {
"Content-Type": object.httpMetadata?.contentType ?? "application/octet-stream",
"Cache-Control": "public, max-age=31536000",
},
});
},
};
This is a simple CDN over R2 in about 15 lines. Content is served from Cloudflare’s edge — no separate CDN configuration needed.
R2 public buckets. You can expose a bucket publicly through a Cloudflare-managed domain, which serves content through Cloudflare’s cache automatically. No need to put CloudFront in front of S3.
S3-compatible API. R2 implements the S3 API, which means any tool that works with S3 — AWS SDK, rclone, s3cmd, boto3 — works with R2 with a credential change.
// AWS SDK v3 pointed at R2
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
const r2 = new S3Client({
region: "auto",
endpoint: `https://${ACCOUNT_ID}.r2.cloudflarestorage.com`,
credentials: {
accessKeyId: R2_ACCESS_KEY_ID,
secretAccessKey: R2_SECRET_ACCESS_KEY,
},
});
await r2.send(new PutObjectCommand({
Bucket: "my-bucket",
Key: "uploads/image.png",
Body: fileBuffer,
ContentType: "image/png",
}));
The endpoint URL and credentials differ; the rest of the code is identical to S3.
Migration from S3 to R2
For existing S3 buckets, the migration path is:
- Create the R2 bucket.
- Sync existing objects using rclone:
rclone copy s3:my-bucket r2:my-bucket --progress
- Update your application to use R2 credentials and endpoint.
- Update CDN or public URL configuration.
- Set up lifecycle rules in R2 if needed.
- Delete the S3 bucket after confirming the migration.
The main risk is URL stability. If you’ve distributed S3 URLs publicly — in emails, social media, indexed by search engines — those URLs will break when the bucket moves. For user-facing content that you control (images referenced in your database or code), migration is clean. For content with permanent public URLs, you need redirect rules or a longer migration window.
Which to Choose
The decision comes down to a few questions:
How read-heavy is your workload? If you’re serving a lot of content to users (images, videos, downloads), R2’s zero egress pricing saves real money. If your storage is mostly write-heavy with infrequent reads (backups, logs), the egress difference is minimal.
Are you in the AWS ecosystem? If you’re using Lambda triggers, Athena, SageMaker, or other AWS services that integrate with S3, staying on S3 avoids re-architecting those workflows. R2 doesn’t replicate the AWS integration surface.
Are you on Cloudflare Workers? If yes, R2 is the obvious choice — the Workers binding is simple, the integration is tight, and you stay in one ecosystem.
Do you need advanced compliance features? S3’s Object Lock, CloudTrail integration, and compliance certifications are more mature. For HIPAA, financial services, or other regulated environments, verify R2’s current certifications against your requirements.
For new projects that serve content to users and aren’t tied to the AWS ecosystem: R2 is worth using. For projects already deep in AWS that aren’t primarily serving content: staying on S3 is the path of least resistance. The egress savings don’t outweigh the migration and integration costs for every workload — only for the ones where egress is actually a significant cost.
Sponsored
More from this category
More from Cloud & Infrastructure
Error Tracking in 2026: What Sentry Catches That Your Logs Don't
Vendor Lock-in in 2026: What It Actually Costs and When to Stop Worrying About It
ClickHouse in 2026: Analytical Queries on Billions of Rows Without the Pain
Sponsored
Discussion
Join the conversation.
Comments are powered by GitHub Discussions. Sign in with your GitHub account to leave a comment.
Sponsored