Cloudflare R2 Free Tier: Zero Egress, Real Storage, No Credit Card Trap
If you’ve ever been burned by a surprise AWS S3 bill because users downloaded your images too many times, Cloudflare R2 exists specifically to fix that problem. It’s object storage that doesn’t charge you for bandwidth — ever. And the free tier is genuinely usable for real projects, not just toy demos.
This guide walks through what’s actually included, where R2 shines, how to set it up from scratch, and how it compares to the alternatives most developers consider first.
What is Cloudflare R2?
Cloudflare R2 is S3-compatible object storage that runs on Cloudflare’s global network. The two things that make it different from every other S3 alternative:
- Zero egress fees. You pay nothing when files are downloaded, regardless of volume.
- Native S3 API compatibility. Existing AWS SDKs, CLI tools, and libraries work with minimal changes — usually just swapping the endpoint URL.
R2 launched publicly in 2022 and has steadily added features that used to be S3-only: lifecycle rules, presigned URLs, CORS configuration, custom domains, and integration with Cloudflare Workers. It’s now a realistic drop-in replacement for S3 in most applications.
The pitch is simple: store files, serve files, never get a bandwidth bill. For a directory like stacksfree.com that tracks genuinely free tools, R2 is one of the few “free tier that scales” options in the storage category.
Key Free Tier Features & Limits
Cloudflare R2’s free tier is permanent (not a 12-month trial like AWS) and includes:
| Resource | Free Monthly Allowance |
|---|---|
| Storage | 10 GB-month |
| Class A operations (writes, lists) | 1,000,000 |
| Class B operations (reads) | 10,000,000 |
| Egress bandwidth | Unlimited ($0) |
What counts as Class A vs Class B
- Class A (write-heavy):
PutObject,ListBucket,CreateMultipartUpload,CompleteMultipartUpload,CopyObject. These are the operations that mutate state or scan buckets. - Class B (read-heavy):
GetObject,HeadObject. Simple reads and metadata checks.
The 10:1 ratio of reads to writes on the free tier matches most web app workloads, where content is consumed far more often than it’s produced.
What happens when you exceed limits
Cloudflare doesn’t cut you off — you simply start paying the usage rates:
- Storage: $0.015 / GB-month
- Class A ops: $4.50 / million
- Class B ops: $0.36 / million
- Egress: still $0
This is the part that makes R2’s pricing model fundamentally different from competitors. Even when you outgrow free, your cost stays predictable because the biggest variable — bandwidth — is always zero.
Best Use Cases (with Real Examples)
1. User-Generated Content Uploads
Profile pictures, document attachments, PDF exports. A typical SaaS app might let users upload a 2MB avatar. With R2:
- Upload via presigned URL directly from the browser (no proxy through your server)
- Serve through a custom domain like
cdn.yourapp.com - Egress stays free even if a power user gets viewed 100,000 times
Real-world pattern: A community forum with 5,000 active users uploading screenshots. Storage stays well under 10GB. Reads can spike to millions per month during viral threads — with S3, that’s a real bill; with R2, it’s still $0.
2. Image and Static Asset Hosting
If you’re building a blog, marketing site, or documentation portal, R2 replaces the “stick images in the repo and hope” anti-pattern. Upload once, reference via URL, and let Cloudflare’s CDN handle delivery.
Pair it with Cloudflare Images or Imgix-style on-the-fly transforms (via Workers) for resizing, and you have a full media pipeline without AWS.
3. Database and File Backups
Nightly PostgreSQL dumps, MySQL snapshots, log archives. The Class A limit of 1M writes/month means you can comfortably write thousands of backup files per day. Storing 10GB of compressed backups covers a substantial database history.
Practical setup: A cron job runs pg_dump | gzip | aws s3 cp - s3://your-bucket/backup-$(date +%F).sql.gz pointing at your R2 endpoint. Rotate old files with lifecycle rules.
4. Migrating Off AWS S3
This is the most common production use case. The migration path:
- Use
rcloneoraws s3 syncto copy your existing bucket to R2 - Update your application’s endpoint URL from
s3.amazonaws.comto<account-id>.r2.cloudflarestorage.com - Keep the same SDK, same IAM-style credentials, same code
For most apps, the change is a single environment variable. The bandwidth savings are immediate and permanent.
Step-by-Step Setup Guide
Step 1: Create a Cloudflare account
Go to dash.cloudflare.com and sign up. You don’t need an existing domain registered with Cloudflare to use R2 — but having one makes custom domain setup trivial later.
Important: Add a payment method even on the free tier. Cloudflare requires it to verify you’re a real account, but you won’t be charged unless you explicitly exceed free limits and they’re enabled for billing.
Step 2: Enable R2
From the dashboard sidebar, click R2 Object Storage. Accept the terms. You’ll land on the R2 overview page.
Step 3: Create your first bucket
- Click Create bucket
- Name it (must be globally unique, lowercase, DNS-safe — e.g.,
my-app-uploads-prod) - Pick a location hint if you care about latency:
- Automatic (default) — Cloudflare picks the closest region to your first write
- North America —
wnam(Western) orenam(Eastern) - Europe —
weuroreeur - Asia Pacific —
apac
- Click Create
Step 4: Generate API credentials
R2 uses S3-compatible auth, so you’ll need an access key ID and secret.
- Go to R2 → Manage R2 API Tokens
- Click Create API Token
- Choose permissions:
- Object Read & Write for most app use cases
- Admin Read & Write only if you need bucket management
- Specify which buckets the token can access (or all buckets)
- Click Create API Token
You’ll see:
- Access Key ID
- Secret Access Key
- Endpoint URL:
https://<account-id>.r2.cloudflarestorage.com - Jurisdiction-specific endpoint if applicable
Save these immediately. The secret is only shown once.
Step 5: Upload files
Using the AWS CLI
Configure a profile:
aws configure --profile r2
# Access Key ID: <your-access-key>
# Secret Access Key: <your-secret-key>
# Default region: auto
# Default output: json
Upload a file:
aws s3 cp photo.jpg \
s3://my-app-uploads-prod/images/photo.jpg \
--endpoint-url https://<account-id>.r2.cloudflarestorage.com \
--profile r2
Using the AWS SDK (Node.js example)
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
const s3 = new S3Client({
region: 'auto',
endpoint: 'https://<account-id>.r2.cloudflarestorage.com',
credentials: {
accessKeyId: process.env.R2_ACCESS_KEY_ID,
secretAccessKey: process.env.R2_SECRET_ACCESS_KEY,
},
});
await s3.send(new PutObjectCommand({
Bucket: 'my-app-uploads-prod',
Key: 'images/photo.jpg',
Body: fileBuffer,
ContentType: 'image/jpeg',
}));
Generating a presigned URL (for direct browser uploads)
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
import { PutObjectCommand } from '@aws-sdk/client-s3';
const url = await getSignedUrl(s3, new PutObjectCommand({
Bucket: 'my-app-uploads-prod',
Key: 'uploads/user-123-avatar.png',
ContentType: 'image/png',
}), { expiresIn: 3600 });
// POST the file directly to this URL from the browser
Step 6: Serve files publicly
By default, R2 buckets are private. To make files publicly accessible:
Option A — Enable public access via r2.dev subdomain
- Go to your bucket → Settings
- Under Public access, enable the
r2.devsubdomain - Files become available at
https://pub-<hash>.r2.dev/<key>
This is fine for testing but not recommended for production — the URL is ugly and Cloudflare may rate-limit it.
Option B — Custom domain (recommended)
- Under Public access, click Connect Domain
- Enter your domain (e.g.,
cdn.yourdomain.com) - Cloudflare auto-configures the DNS record if the domain is on their nameservers
- Files serve at
https://cdn.yourdomain.com/images/photo.jpg
Custom domains get full Cloudflare CDN caching, HTTPS, and analytics for free.
How R2 Compares to Alternatives
Cloudflare R2 vs AWS S3
| Feature | Cloudflare R2 | AWS S3 |
|---|---|---|
| Free storage | 10 GB forever | 5 GB for 12 months only |
| Egress fees | $0 | ~$0.09/GB after 100GB |
| Class A ops (free) | 1M/month | 2,000 PUT/month |
| S3 API compatibility | Yes | Native |
| Multi-region replication | Coming/limited | Mature |
| Ecosystem maturity | Growing | Industry standard |
Verdict: If you’re starting a new project, R2 is the obvious choice unless you need deep AWS integrations (Lambda triggers on S3 events, Macie, Glacier archival tiers). Migrating existing apps is low-risk because the SDK works identically.
User sentiment: The most common praise on Hacker News and Reddit r/aws threads: “My S3 bill dropped dramatically after moving media to R2.” The most common complaint: occasional S3 API edge cases (particularly around multipart uploads and IAM policy features) aren’t fully implemented yet.
Cloudflare R2 vs Backblaze B2
| Feature | Cloudflare R2 | Backblaze B2 |
|---|---|---|
| Free storage | 10 GB | 10 GB |
| Free Class A ops | 1M/month | 2,500/day (~75K/month) |
| Egress | Free | Free up to 3x stored data/day |
| S3-compatible API | Yes | Yes (via S3-compatible endpoint) |
| Pricing beyond free | $0.015/GB | $0.006/GB + egress |
Verdict: B2 has cheaper raw storage but the egress fees kill it for media-heavy apps. B2’s free egress allowance (3x stored data) sounds generous until you realize 30GB of daily egress on the 10GB free tier vanishes instantly if a single image goes viral. R2 wins for anything user-facing.
Cloudflare R2 vs Cloudinary
These solve different problems, but developers often compare them for image hosting:
| Feature | Cloudflare R2 | Cloudinary |
|---|---|---|
| Purpose | Raw object storage | Image/video transformation + CDN |
| Free storage | 10 GB | ~25 GB (credit-based) |
| Free bandwidth | Unlimited | ~25 GB/month |
| On-the-fly transforms | Via Workers (DIY) | Built-in |
| Pricing model | Simple per-GB | Credit-based, complex |
Verdict: Cloudinary is better if you need automatic resizing, format conversion, and AI-powered transformations out of the box. R2 is better if you just need a place to put files and serve them fast. For many apps, the combination of R2 + a Cloudflare Worker doing image transforms replaces Cloudinary entirely — and stays free at much higher volumes.
Tips to Maximize the Free Tier
1. Enforce upload size limits client-side and server-side
The 10GB storage cap can disappear fast if users upload uncompressed video. Implement:
// Hard cap at 5MB before upload
if (file.size > 5 * 1024 * 1024) {
throw new Error('File too large');
}
And validate content-type — don’t trust the extension.
2. Compress and transform before storing
For images:
- Use
sharp(Node.js) orPillow(Python) to resize on upload - Convert PNG → WebP for 25-35% size reduction (WebP docs)
- Strip EXIF data unless you need it
import sharp from 'sharp';
const optimized = await sharp(fileBuffer)
.resize(1200, null, { withoutEnlargement: true })
.webp({ quality: 80 })
.toBuffer();
A typical 4MB phone photo becomes a 150KB WebP — meaning your 10GB free tier holds ~65,000 images instead of ~2,500.
3. Set aggressive cache headers
R2 files served through Cloudflare’s CDN cache for free. Set long TTLs:
await s3.send(new PutObjectCommand({
Bucket: 'my-bucket',
Key: 'assets/logo.png',
Body: buffer,
ContentType: 'image/png',
CacheControl: 'public, max-age=31536000, immutable',
}));
Cached responses don’t count against your Class B operation quota because they never hit R2.
4. Use lifecycle rules for cleanup
Set up automatic deletion of temporary files (upload previews, expired exports):
- Bucket → Settings → Object lifecycle rules
- Rule: Delete objects with prefix
tmp/after 7 days
This prevents orphaned files from slowly eating your storage budget.
5. Avoid unnecessary ListObjects calls
ListBucket is a Class A operation. Don’t run directory listings on every page load. Cache the file manifest in your database or use Cloudflare KV instead.
6. Use Workers for auth-gated access
If you need to gate file access behind user authentication, don’t proxy through your server (that counts as a read on every request). Instead:
- Put a Cloudflare Worker in front of your bucket
- The Worker validates a JWT or session cookie
- If valid, it streams the file from R2 to the user
Workers have their own free tier (100K requests/day) and R2 access from Workers is free.
7. Monitor usage in the dashboard
Cloudflare’s R2 dashboard shows storage and operation counts in real-time. Check it weekly during your first month to catch runaway usage before it becomes a bill.
The Bottom Line
Cloudflare R2’s free tier is one of the most genuinely useful free offerings in the storage space. 10GB of storage, 10 million reads, and zero egress fees covers a wide range of real applications — from hobby projects to small SaaS products — without ever asking for a credit card.
The S3 compatibility means you’re not locked into Cloudflare’s ecosystem. If you ever outgrow the free tier, paid pricing is transparent and the bandwidth-is-free model protects you from the unpredictable bills that make AWS such a gamble for indie projects.
For the full profile and comparison with other free-tier storage options, check the Cloudflare R2 listing on stacksfree.com.
References
[1] dash.cloudflare.com [2] WebP docs [3] Cloudflare R2 listing on stacksfree.com
