Skip to main content

Cloudflare R2: Object Storage With No Egress Fees

Cloudflare · 19.04.2026 · 4 min read
Иллюстрация к статье «Cloudflare R2: объектное хранилище без платы за исходящий трафик»

Cloudflare R2 is an object storage service compatible with the S3 API. The main difference from Amazon S3: no charge for egress (outbound traffic). Great for storing images, video, and backups.

R2 Pricing

ResourceFreePaid
Storage10 GB/mo$0.015/GB
Class A operations (PUT)1M/mo$4.50/million
Class B operations (GET)10M/mo$0.36/million
Egress trafficFree ✅Free ✅

Creating an R2 Bucket

R2 → Create bucket → enter a name → Create bucket.

For public access (CDN): Settings → Public Access → Allow Access.

# Public file URL:
https://pub-HASH.r2.dev/folder/filename.jpg

# Or a custom domain:
https://media.example.com/folder/filename.jpg

Uploading Files via the S3 API

# Configure aws-cli for R2
aws configure --profile cloudflare
# AWS Access Key ID: R2 Access Key
# AWS Secret Access Key: R2 Secret Key
# Default region: auto

# Upload a file
aws s3 cp file.jpg s3://my-bucket/file.jpg     --endpoint-url https://ACCOUNT_ID.r2.cloudflarestorage.com     --profile cloudflare

# Sync a folder
aws s3 sync ./media/ s3://my-bucket/     --endpoint-url https://ACCOUNT_ID.r2.cloudflarestorage.com     --profile cloudflare

WordPress + R2 via a Plugin

Use the "Media Cloud" or "Offload Media" plugin with these R2 settings:

  • Storage Provider: S3 Compatible
  • Endpoint: https://ACCOUNT_ID.r2.cloudflarestorage.com
  • Region: auto
✅ R2 is ideal for offloading your server: store WordPress images in R2 and serve them via Cloudflare CDN — no traffic fees and high speed.

R2 compared to S3 and local disk

CriterionR2Amazon S3Local server disk
Egress feesNoneCharged, by volumeNone (included in server plan)
S3-compatible APIYesYes (the reference)No
Built-in CDNVia CloudflareNeeds CloudFront separatelyNone
Space limitPractically nonePractically noneVPS disk size

Breaking down common errors

Access Denied when uploading through the S3 API

This usually means the R2 Access Key and Secret Key do not match the bucket (keys are tied to a specific Cloudflare account, not global), or the endpoint URL is wrong — it must contain the Account ID, not the bucket name or a domain.

A CORS error when accessing the bucket from a browser

If files are loaded from R2 directly by client-side JS (not just as an <img src>), the bucket's CORS policy needs configuring: Settings → CORS Policy → list the allowed Origin, methods, and headers. Without it the browser blocks the response at the Same-Origin Policy level, even if the file itself is served with a 200 status.

Verifying the result

# Check that public access to the file works
curl -I https://pub-HASH.r2.dev/folder/filename.jpg

# Expected: HTTP/2 200, content-type: image/jpeg

# List objects with aws-cli
aws s3 ls s3://my-bucket/ --endpoint-url https://ACCOUNT_ID.r2.cloudflarestorage.com --profile cloudflare

When R2 does not fit

  • You need active queries and complex lookups over file metadata — R2, like S3, is object storage, not a database; searching needs a separate index.
  • You need a filesystem with locking and simultaneous writes to one file from several servers — object storage is not built for that; you need NFS or a shared disk.
  • You are storing images or video and need on-the-fly processing (resizing, transcoding) — Cloudflare Images/Stream fits that better than a raw R2 bucket.

R2 supports Object Lifecycle Rules — you can set objects older than a given age (temporary backups, for example) to be deleted automatically or moved to a cheaper storage class. Rules are configured under the bucket's Settings → Object lifecycle rules.

For temporary access to private files without turning on full public access, use a presigned URL — a signed link with a limited lifetime, generated by an SDK or aws-cli through the same S3-compatible API. Such a link serves the file directly, without passing through your own server.

Migrating an existing bucket from Amazon S3 to R2 can be done with rclone or plain aws s3 sync, pointing at both endpoints — the source one and ACCOUNT_ID.r2.cloudflarestorage.com. For large volumes of data, migrate in batches so you do not hit per-request rate limits.

Class A and Class B operation usage, along with storage volume, is visible in Dashboard → R2 → Metrics for each bucket separately — useful when several projects share one Cloudflare account and you need to see what exactly is driving the load.

When creating a bucket you can choose a jurisdiction (for example, EU) — this guarantees the data physically never leaves the chosen region, which matters for projects with data-residency requirements like GDPR.

Was this article helpful?
← Back to Knowledge Base Ask Support