· cloudflare / aws / s3
Cloudflare R2 vs AWS S3 — object storage for devs in 2026
R2 wins every time egress is your dominant cost. S3 wins when you need AWS-native integrations, Glacier-tier archival, or decade-tested compliance.
By Ethan
1,839 words · 10 min read
Pick Cloudflare R2 if you’re paying S3 egress bills. Zero egress fees change the math completely — a 100 GB store with 1 TB of monthly downloads costs about $1.86 on R2 versus $83.70 on S3, a 45× difference. Pick AWS S3 if you need Lambda event triggers, Athena query access, Glacier-tier archival at $0.001/GB, or compliance tooling backed by years of bank audits. The two services overlap more than they differ, but egress pricing and AWS ecosystem depth are the decisive split.
Who this is for
Indie devs and small teams choosing a primary object store in 2026. If your workload is already tightly coupled to AWS services that trigger off S3 events — Lambda, SageMaker, Athena, Glue — this article won’t change your situation: use S3.
What we tested
We examined Cloudflare R2 (generally available since 2022, S3-compatible API, global edge network, Bucket Lock GA since March 2025) and AWS S3 us-east-1 (Standard storage class). Performance figures come from Cloudflare’s own benchmark (blog.cloudflare.com/r2-is-faster-than-s3) and an independent small-object throughput study by Tigris Data. Pricing is drawn directly from the Cloudflare R2 pricing page and AWS S3 pricing page, both accessed June 2026. Code snippets use AWS SDK v3 — the same SDK works against both services.
Pricing: where R2 wins by default
The headline number is egress. S3 charges $0.09/GB after the first 100 GB free each month. R2 charges nothing. Everything else is secondary.
Storage and operations
| R2 Standard | R2 Infrequent Access | S3 Standard | |
|---|---|---|---|
| Storage | $0.015/GB-month | $0.010/GB-month | $0.023/GB-month |
| PUT / LIST requests | $4.50/million | $9.00/million | $5.00/million |
| GET requests | $0.36/million | $0.90/million | $0.40/million |
| Data retrieval | — | $0.01/GB | — |
| Egress | Free | Free | $0.09/GB (after 100 GB) |
R2’s free tier covers 10 GB storage, 1 million Class A operations, and 10 million Class B operations per month. Most solo projects never pay anything.
Real cost scenarios
The following scenarios assume 1 TB of monthly egress — typical for a media-heavy app or SaaS with user-uploaded assets.
| Scenario | R2 | S3 | Savings |
|---|---|---|---|
| 100 GB stored + 1 TB egress/month | ~$1.86 | ~$83.70 | 45× cheaper |
| 1 TB stored + 1 TB egress/month | ~$15.72 | ~$107.11 | 6.8× cheaper |
| 10 TB stored + 1 TB egress/month | ~$153.96 | ~$319.08 | 2× cheaper |
The pattern is clear: R2 always wins when egress exceeds roughly 111 GB/month at any storage scale. Below that threshold, S3’s slightly cheaper per-request pricing can close the gap, but the gap is small.
The one scenario where S3 wins on price: zero-egress archival. S3 Glacier Deep Archive at $0.00099/GB is 94% cheaper than R2 Standard at $0.015/GB. If you’re storing compliance logs or cold backups that you never download, Glacier Deep Archive is the right answer and R2 has nothing that competes with it. For a full cost breakdown across the Cloudflare vs AWS stack — including Workers, D1, and Queues — see Cloudflare vs AWS: the real bill at scale.
S3 API compatibility: drop-in replacement for most things
R2 exposes an S3-compatible HTTP API, which means the AWS SDK v3 works against it with one config change.
import { S3Client } from "@aws-sdk/client-s3";
const S3 = new S3Client({
region: "auto",
endpoint: `https://<ACCOUNT_ID>.r2.cloudflarestorage.com`,
credentials: {
accessKeyId: "<ACCESS_KEY_ID>",
secretAccessKey: "<SECRET_ACCESS_KEY>",
},
});
That is the entire migration for presigned URLs, multipart uploads, and standard GET/PUT operations. Presigned URLs work identically:
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
import { GetObjectCommand, PutObjectCommand } from "@aws-sdk/client-s3";
const getUrl = await getSignedUrl(
S3,
new GetObjectCommand({ Bucket: "my-bucket", Key: "image.png" }),
{ expiresIn: 3600 }
);
const putUrl = await getSignedUrl(
S3,
new PutObjectCommand({
Bucket: "my-bucket",
Key: "image.png",
ContentType: "image/png",
}),
{ expiresIn: 3600 }
);
The R2 S3 API compatibility reference documents which S3 operations are supported. Most of the common ones are: GetObject, PutObject, DeleteObject, CreateMultipartUpload, ListObjectsV2, HeadObject, CopyObject. What’s missing: S3 Select, object tagging, S3 event notifications (no Lambda triggers), and server-side encryption with customer-managed KMS keys.
Workers binding: R2’s actual advantage
If you’re on Cloudflare Workers or Pages, R2’s native binding bypasses the HTTP layer entirely. You get sub-millisecond object access from inside the function, billed at the same per-request rate with no egress cost, and no round-trip to an external endpoint.
// wrangler.toml:
// [[r2_buckets]]
// binding = "MY_BUCKET"
// bucket_name = "my-bucket"
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const key = new URL(request.url).pathname.slice(1);
const object = await env.MY_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" },
});
},
};
This pattern — Worker as the access layer, R2 as the store — eliminates the need for a separate CDN. There is no CloudFront equivalent to configure, no signed distribution to manage. The Worker is both the CDN node and the auth layer. Liveblocks switched to this architecture and published a case study describing it. If you’re deciding between Workers and Lambda more broadly, Cloudflare Workers vs AWS Lambda covers the serverless tradeoffs in detail.
Performance
Cloudflare’s own benchmark shows R2 outperforming S3 on read latency in every region:
| Region | R2 TTLB | S3 TTLB | Advantage |
|---|---|---|---|
| North America | 1,262 ms | 2,055 ms | 38% faster |
| Europe | 1,303 ms | 1,729 ms | 20% faster |
| Asia-Pacific | 4,057 ms | 6,850 ms | 40% faster |
| Cross-region | 3,224 ms | 6,387 ms | ~2× faster |
Source: blog.cloudflare.com/r2-is-faster-than-s3. The methodology is Cloudflare’s own — treat these as directionally correct, not independently verified.
Caveats apply on the write side. The independent Tigris Data small-object benchmark measured R2 PUT throughput for 1 KB objects at 138.8 ops/sec, compared to up to 1,490.2 ops/sec for the best-in-class alternative (Tigris). R2 is optimized for reads from the global edge, not for high-throughput write workloads. If you’re ingesting large volumes of small objects at speed — log pipelines, event streams, high-frequency uploads — test R2’s write performance with your actual object size before committing.
One 2026-specific improvement: R2 Local Uploads (beta, February 2026) reduces cross-region upload TTLB by up to 75% by routing uploads to the nearest Cloudflare data center first. If write latency to R2 was a concern in 2025, benchmark again.
Feature gaps to know about
What S3 has that R2 does not
Event notifications. S3 can trigger Lambda functions, SQS queues, and SNS topics on object creation or deletion. R2 has no equivalent. If your architecture depends on S3 event triggers — thumbnail generation, virus scanning, index updates — this is a hard blocker.
Lifecycle transitions across storage classes. S3 can automatically move objects to Infrequent Access, then Glacier, then Deep Archive, based on age rules. R2 has Standard and Infrequent Access tiers, but no Glacier equivalent and no automatic tiering policies.
Athena, SageMaker, Glue. AWS analytics and ML services speak S3 natively. Querying R2 data from Athena requires exporting it to S3 first.
Object Lock maturity. S3 Object Lock has been in use for compliance (WORM) for years. R2 launched Bucket Lock GA in March 2025. The feature works, but the compliance audit history isn’t there yet. If your legal team is writing the requirements, ask them whether “GA since March 2025” satisfies their timeline.
Multi-region replication. S3 supports cross-region replication with configurable rules. R2 does not have a managed replication feature — Cloudflare’s global edge serves R2 reads from any region, but the data itself lives in one location.
What R2 has that S3 does not
Zero egress. Said it once. Worth saying again. For content-heavy apps, egress is typically the biggest line item on the cloud bill.
Workers binding. Native, zero-latency object access from inside a Worker function. No equivalent in Lambda without calling S3 over HTTP.
Simpler pricing. R2 has two tiers (Standard, Infrequent Access) and three dimensions (storage, Class A ops, Class B ops). S3 has storage tiers, request pricing, egress pricing, early deletion fees, retrieval fees, and a pricing page long enough to need its own scroll indicator.
Migrating from S3 to R2
Cloudflare provides Super Slurper, a managed migration tool that copies objects from an S3 bucket to an R2 bucket without the data touching your application. As of February 2026, Super Slurper runs 5× faster than its original launch performance.
The migration path for the application layer is the AWS SDK config change shown above. Most apps can flip an environment variable and be done.
What to check before migrating:
- Any Lambda triggers on S3 events need a replacement — R2 has no equivalent
- Server-side encryption with customer KMS keys is not supported on R2
- Bucket policies and ACLs differ; R2 uses a simpler model
For a full migration playbook — covering Lambda, API Gateway, and databases alongside R2 — see How to Migrate from AWS to Cloudflare in 2026.
Verdict
Pick R2 if:
- Egress is a meaningful cost in your S3 bill
- You’re building on Cloudflare Workers or Pages
- You want to serve assets globally without paying for CloudFront
- Your project is under 10 TB and cost-sensitive
Pick S3 if:
- You need Lambda event triggers, Athena, SageMaker, or Glue integration
- Your compliance requirements need Object Lock with multi-year audit history
- You’re storing cold backups (Glacier Deep Archive is 94% cheaper than R2 Standard)
- Your workload involves high-throughput writes of small objects
- You need multi-region replication
The default recommendation for a new project in 2026 with meaningful user-uploaded content or media serving: start on R2. The S3 SDK compatibility means switching back is a config change, not a rewrite.
If you’re running R2 on Cloudflare Workers, Cloudflare Pro or Business plans add SLAs, enhanced DDoS protection, and priority support — worth it once your app has paying customers depending on uptime.
Caveats
Performance benchmarks are from Cloudflare’s own testing. Independent verification exists for small-object write throughput (Tigris Data, linked below) but not for the TTLB figures. S3 has regional pricing variations; figures used here are us-east-1 Standard. AWS S3 Express One Zone received up to 85% price cuts in early 2025 and may be competitive for latency-sensitive single-AZ workloads — this article does not cover that tier. Affiliate links to Cloudflare are present; see the disclosure above. AWS has no affiliate relationship with toolchew.
References
- Cloudflare R2 Pricing
- AWS S3 Pricing
- R2 S3 API Compatibility
- R2 Workers API Reference
- R2 Presigned URLs
- Cloudflare Blog — R2 is faster than S3
- Super Slurper — R2 Data Migration
- Super Slurper 5× faster (Feb 2026 changelog)
- S3 Express One Zone price cuts
- Liveblocks case study — Cloudflare R2
- Tigris Data — small object benchmark
- R2 changelog