Cloudflare R2 vs S3: Object Storage for VPS Apps
Source: Dev.to
Typical VPS workloads
Object storage is your durable “bucket of blobs” for:
- User uploads (images, videos, PDFs)
- Backups and archives
- Static assets for web apps
- Data pipelines (logs, exports)
Deciding factors
- Egress costs – moving data out to the public internet or to your VPS
- Latency – distance from your compute to the storage endpoint
- S3 API compatibility – tooling and SDK friction
- Operational ergonomics – IAM, policies, lifecycle rules
Why R2 stands out
- Zero egress fees in many common scenarios, which can flip the cost math for serving files or syncing content across regions.
- Tight integration with Cloudflare’s edge network and caching layer.
Note: “No egress” does not mean “no cost.” You still pay for storage and operations, and request pricing should be validated against your access pattern.
Cost considerations
- S3: Storage pricing is usually modest; networking (egress + request fees) often dominates, especially for public downloads or chatty workloads.
- R2: Eliminates most egress fees, but you still incur storage and request charges.
If your app is asset‑heavy (images, downloads, media), model egress first. A sudden traffic spike on a small VPS can make S3 egress a surprise that forces a redesign.
Latency & geography
- If your VPS sits near an AWS region, S3 latency is predictable.
- For globally distributed users, a CDN in front of either service often matters more than raw bucket latency.
- R2 lives closer to Cloudflare’s edge, making it a natural fit when you already use Cloudflare’s CDN/DNS.
Feature comparison
| Feature | Amazon S3 | Cloudflare R2 |
|---|---|---|
| Durability & ecosystem maturity | ✔️ Great durability, massive ecosystem | ✔️ Good durability, growing ecosystem |
| Egress pricing | Paid per GB (can be costly) | Free in many scenarios |
| Request pricing | Paid per request | Paid per request (generally comparable) |
| IAM & policy tooling | Mature, fine‑grained | Simpler, still evolving |
| Lifecycle management | Deep options (transition, expiration) | Basic lifecycle rules |
| Broad integration | Backups, CI/CD, data platforms, many third‑party tools | Compatible with many S3‑aware tools, but fewer native integrations |
| S3 compatibility | Native | S3‑compatible for most operations (most SDK calls work) |
| Edge‑case behavior | Full AWS feature set | May differ on advanced IAM or specific AWS integrations |
Tip: If you rely on edge‑case S3 behaviors, test your toolchain against R2 before committing.
Minimal Node.js example (AWS SDK v3)
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
import fs from "node:fs";
const client = new S3Client({
region: "auto",
endpoint: process.env.R2_ENDPOINT, // e.g. https://.r2.cloudflarestorage.com
credentials: {
accessKeyId: process.env.R2_ACCESS_KEY_ID,
secretAccessKey: process.env.R2_SECRET_ACCESS_KEY,
},
});
async function upload() {
const Body = fs.createReadStream("./avatar.png");
const cmd = new PutObjectCommand({
Bucket: process.env.R2_BUCKET,
Key: "uploads/avatar.png",
Body,
ContentType: "image/png",
});
await client.send(cmd);
console.log("Uploaded");
}
upload().catch(console.error);
If you can upload, list, and read objects with your existing tooling, most migrations become a pricing decision rather than a rewrite.
Decision guide
- Choose S3 if you’re deeply tied to AWS services, need the most mature IAM/policy surface, or want maximum “it just works” integration with third‑party tools.
- Choose Cloudflare R2 if egress is a major risk, you serve lots of public content, or you want an S3‑like workflow without typical bandwidth penalties.
For lean VPS stacks (e.g., DigitalOcean droplets or Hetzner servers), R2 can keep bills predictable while still using familiar S3 tooling. If you already use Cloudflare for DNS/CDN, the operational simplicity is a nice bonus—but it shouldn’t be the sole reason for the switch.