cloudflare-r2
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseCloudflare R2
Cloudflare R2
S3-compatible object storage with zero egress fees, built on Cloudflare's global network.
基于Cloudflare全球网络构建的S3兼容对象存储服务,零流出费用。
When to Use
适用场景
- Storing user uploads, media files, backups, or static assets.
- Replacing AWS S3 to eliminate egress costs for read-heavy workloads.
- Serving files at the edge via Workers or public bucket access.
- Building multi-cloud storage that avoids vendor lock-in (S3 API compatible).
- Storing ML model artifacts, training data, or inference results.
- 存储用户上传文件、媒体文件、备份数据或静态资源。
- 替代AWS S3,消除读密集型工作负载的流出成本。
- 通过Workers或公开存储桶访问在边缘节点提供文件服务。
- 构建避免厂商锁定的多云存储方案(兼容S3 API)。
- 存储机器学习模型工件、训练数据或推理结果。
Prerequisites
前置条件
- Cloudflare account with R2 enabled (dashboard > R2 > subscribe).
- Wrangler CLI v3+ installed: .
npm install -g wrangler - Authenticated via or
wrangler login.CLOUDFLARE_API_TOKEN - For S3 API access: R2 API token created under R2 > Manage R2 API Tokens.
- 已启用R2的Cloudflare账号(控制台 > R2 > 订阅)。
- 已安装Wrangler CLI v3+:。
npm install -g wrangler - 通过或
wrangler login完成身份验证。CLOUDFLARE_API_TOKEN - 若使用S3 API访问:需在R2 > 管理R2 API令牌下创建R2 API令牌。
Bucket Management with Wrangler
使用Wrangler管理存储桶
Create and List Buckets
创建与列出存储桶
bash
undefinedbash
undefinedCreate a new bucket
创建新存储桶
npx wrangler r2 bucket create app-assets
npx wrangler r2 bucket create app-assets
Create a bucket in a specific region (hint for data locality)
在指定区域创建存储桶(提示数据本地化)
npx wrangler r2 bucket create eu-uploads --location=eu
npx wrangler r2 bucket create eu-uploads --location=eu
List all buckets
列出所有存储桶
npx wrangler r2 bucket list
npx wrangler r2 bucket list
Delete an empty bucket
删除空存储桶
npx wrangler r2 bucket delete old-bucket
undefinednpx wrangler r2 bucket delete old-bucket
undefinedObject Operations
对象操作
bash
undefinedbash
undefinedUpload a single file
上传单个文件
npx wrangler r2 object put app-assets/images/logo.png --file=./logo.png
npx wrangler r2 object put app-assets/images/logo.png --file=./logo.png
Upload with content type
指定内容类型上传
npx wrangler r2 object put app-assets/data/report.json
--file=./report.json
--content-type="application/json"
--file=./report.json
--content-type="application/json"
npx wrangler r2 object put app-assets/data/report.json
--file=./report.json
--content-type="application/json"
--file=./report.json
--content-type="application/json"
Download an object
下载对象
npx wrangler r2 object get app-assets/images/logo.png --file=./downloaded-logo.png
npx wrangler r2 object get app-assets/images/logo.png --file=./downloaded-logo.png
Delete an object
删除对象
npx wrangler r2 object delete app-assets/images/old-logo.png
npx wrangler r2 object delete app-assets/images/old-logo.png
Get object metadata
获取对象元数据
npx wrangler r2 object head app-assets/images/logo.png
undefinednpx wrangler r2 object head app-assets/images/logo.png
undefinedS3-Compatible API Access
兼容S3的API访问
R2 supports the S3 API, so existing tools (AWS CLI, boto3, s3cmd) work out of the box.
R2支持S3 API,因此现有工具(AWS CLI、boto3、s3cmd)可直接使用。
Generate R2 API Tokens
生成R2 API令牌
- Go to R2 > Manage R2 API Tokens > Create API token.
- Select permissions: Object Read & Write, or Object Read only.
- Scope to specific buckets if possible.
- Save the Access Key ID and Secret Access Key.
- 前往R2 > 管理R2 API令牌 > 创建API令牌。
- 选择权限:对象读写,或仅对象读取。
- 如有可能,限定到特定存储桶。
- 保存访问密钥ID和秘密访问密钥。
AWS CLI Configuration
AWS CLI配置
bash
undefinedbash
undefinedConfigure a named profile for R2
为R2配置命名配置文件
aws configure --profile r2
aws configure --profile r2
Access Key ID: <your-r2-access-key>
Access Key ID: <your-r2-access-key>
Secret Access Key: <your-r2-secret-key>
Secret Access Key: <your-r2-secret-key>
Region: auto
Region: auto
Output: json
Output: json
Use the R2 endpoint
使用R2端点
export R2_ENDPOINT="https://<ACCOUNT_ID>.r2.cloudflarestorage.com"
export R2_ENDPOINT="https://<ACCOUNT_ID>.r2.cloudflarestorage.com"
List buckets
列出存储桶
aws s3 ls --endpoint-url=$R2_ENDPOINT --profile=r2
aws s3 ls --endpoint-url=$R2_ENDPOINT --profile=r2
Sync a directory
同步目录
aws s3 sync ./dist s3://app-assets/static/
--endpoint-url=$R2_ENDPOINT
--profile=r2
--endpoint-url=$R2_ENDPOINT
--profile=r2
aws s3 sync ./dist s3://app-assets/static/
--endpoint-url=$R2_ENDPOINT
--profile=r2
--endpoint-url=$R2_ENDPOINT
--profile=r2
Copy a file
复制文件
aws s3 cp ./backup.tar.gz s3://app-assets/backups/backup-$(date +%Y%m%d).tar.gz
--endpoint-url=$R2_ENDPOINT
--profile=r2
--endpoint-url=$R2_ENDPOINT
--profile=r2
aws s3 cp ./backup.tar.gz s3://app-assets/backups/backup-$(date +%Y%m%d).tar.gz
--endpoint-url=$R2_ENDPOINT
--profile=r2
--endpoint-url=$R2_ENDPOINT
--profile=r2
List objects with prefix
列出带前缀的对象
aws s3 ls s3://app-assets/images/
--endpoint-url=$R2_ENDPOINT
--profile=r2
--endpoint-url=$R2_ENDPOINT
--profile=r2
aws s3 ls s3://app-assets/images/
--endpoint-url=$R2_ENDPOINT
--profile=r2
--endpoint-url=$R2_ENDPOINT
--profile=r2
Remove objects by prefix
删除带前缀的对象
aws s3 rm s3://app-assets/tmp/ --recursive
--endpoint-url=$R2_ENDPOINT
--profile=r2
--endpoint-url=$R2_ENDPOINT
--profile=r2
undefinedaws s3 rm s3://app-assets/tmp/ --recursive
--endpoint-url=$R2_ENDPOINT
--profile=r2
--endpoint-url=$R2_ENDPOINT
--profile=r2
undefinedPython boto3 Client
Python boto3客户端
python
import boto3
s3 = boto3.client(
"s3",
endpoint_url="https://<ACCOUNT_ID>.r2.cloudflarestorage.com",
aws_access_key_id="<R2_ACCESS_KEY>",
aws_secret_access_key="<R2_SECRET_KEY>",
region_name="auto",
)python
import boto3
s3 = boto3.client(
"s3",
endpoint_url="https://<ACCOUNT_ID>.r2.cloudflarestorage.com",
aws_access_key_id="<R2_ACCESS_KEY>",
aws_secret_access_key="<R2_SECRET_KEY>",
region_name="auto",
)Upload file
上传文件
s3.upload_file("./report.pdf", "app-assets", "reports/report.pdf")
s3.upload_file("./report.pdf", "app-assets", "reports/report.pdf")
Generate presigned URL (valid for 1 hour)
生成预签名URL(有效期1小时)
url = s3.generate_presigned_url(
"get_object",
Params={"Bucket": "app-assets", "Key": "reports/report.pdf"},
ExpiresIn=3600,
)
print(url)
url = s3.generate_presigned_url(
"get_object",
Params={"Bucket": "app-assets", "Key": "reports/report.pdf"},
ExpiresIn=3600,
)
print(url)
List objects
列出对象
response = s3.list_objects_v2(Bucket="app-assets", Prefix="images/", MaxKeys=100)
for obj in response.get("Contents", []):
print(f"{obj['Key']} - {obj['Size']} bytes")
undefinedresponse = s3.list_objects_v2(Bucket="app-assets", Prefix="images/", MaxKeys=100)
for obj in response.get("Contents", []):
print(f"{obj['Key']} - {obj['Size']} bytes")
undefinedWorker Bindings
Worker绑定
Bind R2 buckets to Workers or Pages Functions for server-side access without API tokens.
将R2存储桶绑定到Workers或Pages Functions,无需API令牌即可实现服务器端访问。
Wrangler Configuration
Wrangler配置
toml
undefinedtoml
undefinedwrangler.toml
wrangler.toml
name = "asset-worker"
main = "src/index.ts"
compatibility_date = "2024-09-01"
[[r2_buckets]]
binding = "ASSETS"
bucket_name = "app-assets"
[[r2_buckets]]
binding = "UPLOADS"
bucket_name = "user-uploads"
undefinedname = "asset-worker"
main = "src/index.ts"
compatibility_date = "2024-09-01"
[[r2_buckets]]
binding = "ASSETS"
bucket_name = "app-assets"
[[r2_buckets]]
binding = "UPLOADS"
bucket_name = "user-uploads"
undefinedWorker with R2 Operations
集成R2操作的Worker
typescript
// src/index.ts
interface Env {
ASSETS: R2Bucket;
UPLOADS: R2Bucket;
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const url = new URL(request.url);
// GET — serve file from R2
if (request.method === "GET") {
const key = url.pathname.slice(1); // strip leading /
const object = await env.ASSETS.get(key);
if (!object) {
return new Response("Not Found", { status: 404 });
}
const headers = new Headers();
object.writeHttpMetadata(headers);
headers.set("etag", object.httpEtag);
headers.set("cache-control", "public, max-age=86400");
return new Response(object.body, { headers });
}
// PUT — upload file to R2
if (request.method === "PUT") {
const key = url.pathname.slice(1);
const contentType = request.headers.get("content-type") || "application/octet-stream";
await env.UPLOADS.put(key, request.body, {
httpMetadata: { contentType },
customMetadata: { uploadedAt: new Date().toISOString() },
});
return new Response(JSON.stringify({ key, status: "uploaded" }), {
headers: { "Content-Type": "application/json" },
});
}
// DELETE — remove file
if (request.method === "DELETE") {
const key = url.pathname.slice(1);
await env.UPLOADS.delete(key);
return new Response(null, { status: 204 });
}
return new Response("Method Not Allowed", { status: 405 });
},
};typescript
// src/index.ts
interface Env {
ASSETS: R2Bucket;
UPLOADS: R2Bucket;
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const url = new URL(request.url);
// GET — 从R2提供文件
if (request.method === "GET") {
const key = url.pathname.slice(1); // 去除开头的/
const object = await env.ASSETS.get(key);
if (!object) {
return new Response("Not Found", { status: 404 });
}
const headers = new Headers();
object.writeHttpMetadata(headers);
headers.set("etag", object.httpEtag);
headers.set("cache-control", "public, max-age=86400");
return new Response(object.body, { headers });
}
// PUT — 上传文件到R2
if (request.method === "PUT") {
const key = url.pathname.slice(1);
const contentType = request.headers.get("content-type") || "application/octet-stream";
await env.UPLOADS.put(key, request.body, {
httpMetadata: { contentType },
customMetadata: { uploadedAt: new Date().toISOString() },
});
return new Response(JSON.stringify({ key, status: "uploaded" }), {
headers: { "Content-Type": "application/json" },
});
}
// DELETE — 删除文件
if (request.method === "DELETE") {
const key = url.pathname.slice(1);
await env.UPLOADS.delete(key);
return new Response(null, { status: 204 });
}
return new Response("Method Not Allowed", { status: 405 });
},
};Presigned URL Generation in a Worker
在Worker中生成预签名URL
typescript
// Generate time-limited signed URLs using Workers
import { AwsClient } from "aws4fetch";
interface Env {
R2_ACCESS_KEY: string;
R2_SECRET_KEY: string;
R2_ACCOUNT_ID: string;
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const aws = new AwsClient({
accessKeyId: env.R2_ACCESS_KEY,
secretAccessKey: env.R2_SECRET_KEY,
});
const url = new URL(request.url);
const key = url.searchParams.get("key");
if (!key) return new Response("Missing key", { status: 400 });
const r2Url = `https://${env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com/app-assets/${key}`;
const signed = await aws.sign(new Request(r2Url), {
aws: { signQuery: true },
});
return Response.json({ url: signed.url });
},
};typescript
// 使用Workers生成限时签名URL
import { AwsClient } from "aws4fetch";
interface Env {
R2_ACCESS_KEY: string;
R2_SECRET_KEY: string;
R2_ACCOUNT_ID: string;
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const aws = new AwsClient({
accessKeyId: env.R2_ACCESS_KEY,
secretAccessKey: env.R2_SECRET_KEY,
});
const url = new URL(request.url);
const key = url.searchParams.get("key");
if (!key) return new Response("Missing key", { status: 400 });
const r2Url = `https://${env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com/app-assets/${key}`;
const signed = await aws.sign(new Request(r2Url), {
aws: { signQuery: true },
});
return Response.json({ url: signed.url });
},
};Public Bucket Access
公开存储桶访问
Enable public access to serve files directly without a Worker.
- Go to R2 > bucket > Settings > Public access.
- Enable and set a custom domain (e.g., ).
assets.example.com - Objects are accessible at .
https://assets.example.com/<key>
bash
undefined启用公开访问,无需Worker即可直接提供文件服务。
- 前往R2 > 存储桶 > 设置 > 公开访问。
- 启用并设置自定义域名(例如)。
assets.example.com - 对象可通过访问。
https://assets.example.com/<key>
bash
undefinedOr enable via the r2.dev subdomain (for testing)
或通过r2.dev子域名启用(用于测试)
Bucket Settings > R2.dev subdomain > Allow Access
存储桶设置 > R2.dev子域名 > 允许访问
URL: https://pub-<hash>.r2.dev/<key>
地址:https://pub-<hash>.r2.dev/<key>
undefinedundefinedLifecycle Rules
生命周期规则
Configure automatic object expiration or transition.
bash
undefined配置自动对象过期或转换规则。
bash
undefinedSet lifecycle rules via the Cloudflare dashboard:
通过Cloudflare控制台设置生命周期规则:
R2 > bucket > Settings > Object lifecycle rules
R2 > 存储桶 > 设置 > 对象生命周期规则
Or via API
或通过API设置
curl -X PUT "https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/r2/buckets/app-assets/lifecycle"
-H "Authorization: Bearer $CF_API_TOKEN"
-H "Content-Type: application/json"
-d '{ "rules": [ { "id": "expire-tmp-files", "enabled": true, "conditions": { "prefix": "tmp/" }, "actions": { "deleteObject": { "daysAfterCreationDate": 7 } } }, { "id": "expire-old-logs", "enabled": true, "conditions": { "prefix": "logs/" }, "actions": { "deleteObject": { "daysAfterCreationDate": 90 } } } ] }'
-H "Authorization: Bearer $CF_API_TOKEN"
-H "Content-Type: application/json"
-d '{ "rules": [ { "id": "expire-tmp-files", "enabled": true, "conditions": { "prefix": "tmp/" }, "actions": { "deleteObject": { "daysAfterCreationDate": 7 } } }, { "id": "expire-old-logs", "enabled": true, "conditions": { "prefix": "logs/" }, "actions": { "deleteObject": { "daysAfterCreationDate": 90 } } } ] }'
undefinedcurl -X PUT "https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/r2/buckets/app-assets/lifecycle"
-H "Authorization: Bearer $CF_API_TOKEN"
-H "Content-Type: application/json"
-d '{ "rules": [ { "id": "expire-tmp-files", "enabled": true, "conditions": { "prefix": "tmp/" }, "actions": { "deleteObject": { "daysAfterCreationDate": 7 } } }, { "id": "expire-old-logs", "enabled": true, "conditions": { "prefix": "logs/" }, "actions": { "deleteObject": { "daysAfterCreationDate": 90 } } } ] }'
-H "Authorization: Bearer $CF_API_TOKEN"
-H "Content-Type: application/json"
-d '{ "rules": [ { "id": "expire-tmp-files", "enabled": true, "conditions": { "prefix": "tmp/" }, "actions": { "deleteObject": { "daysAfterCreationDate": 7 } } }, { "id": "expire-old-logs", "enabled": true, "conditions": { "prefix": "logs/" }, "actions": { "deleteObject": { "daysAfterCreationDate": 90 } } } ] }'
undefinedCORS Configuration
CORS配置
bash
undefinedbash
undefinedSet CORS policy for browser-based uploads
为基于浏览器的上传设置CORS策略
curl -X PUT "https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/r2/buckets/app-assets/cors"
-H "Authorization: Bearer $CF_API_TOKEN"
-H "Content-Type: application/json"
-d '{ "corsRules": [ { "allowedOrigins": ["https://example.com"], "allowedMethods": ["GET", "PUT", "HEAD"], "allowedHeaders": ["Content-Type", "Authorization"], "maxAgeSeconds": 3600 } ] }'
-H "Authorization: Bearer $CF_API_TOKEN"
-H "Content-Type: application/json"
-d '{ "corsRules": [ { "allowedOrigins": ["https://example.com"], "allowedMethods": ["GET", "PUT", "HEAD"], "allowedHeaders": ["Content-Type", "Authorization"], "maxAgeSeconds": 3600 } ] }'
undefinedcurl -X PUT "https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/r2/buckets/app-assets/cors"
-H "Authorization: Bearer $CF_API_TOKEN"
-H "Content-Type: application/json"
-d '{ "corsRules": [ { "allowedOrigins": ["https://example.com"], "allowedMethods": ["GET", "PUT", "HEAD"], "allowedHeaders": ["Content-Type", "Authorization"], "maxAgeSeconds": 3600 } ] }'
-H "Authorization: Bearer $CF_API_TOKEN"
-H "Content-Type: application/json"
-d '{ "corsRules": [ { "allowedOrigins": ["https://example.com"], "allowedMethods": ["GET", "PUT", "HEAD"], "allowedHeaders": ["Content-Type", "Authorization"], "maxAgeSeconds": 3600 } ] }'
undefinedTroubleshooting
故障排查
| Symptom | Cause | Fix |
|---|---|---|
| Wrong endpoint or bucket name | Verify endpoint is |
| Incorrect secret key or endpoint mismatch | Regenerate R2 API token; ensure region is |
| Uploads succeed but GET returns 404 | Key path mismatch (leading slash) | R2 keys should not start with |
| Slow uploads for large files | Single-stream upload | Use multipart upload; set |
| CORS errors in browser | Missing CORS config on bucket | Add CORS rules for your origin domain |
Worker binding returns | | Verify |
| Public access returns 403 | Public access not enabled | Enable in bucket Settings > Public access |
| 症状 | 原因 | 解决方法 |
|---|---|---|
通过S3 API访问时出现 | 端点或存储桶名称错误 | 验证端点为 |
| 秘密密钥错误或端点不匹配 | 重新生成R2 API令牌;确保区域设置为 |
| 上传成功但GET请求返回404 | 密钥路径不匹配(开头带斜杠) | R2密钥不应以 |
| 大文件上传缓慢 | 单流上传 | 使用分块上传;在wrangler中设置 |
| 浏览器中出现CORS错误 | 存储桶缺少CORS配置 | 为你的源域名添加CORS规则 |
Worker绑定返回 | | 验证 |
| 公开访问返回403 | 未启用公开访问 | 在存储桶设置 > 公开访问中启用 |
Related Skills
相关技能
- cloudflare-workers - Signed URL generation and edge file serving
- cloudflare-pages - Pages Functions with R2 bindings
- cdn-setup - CDN configuration for asset delivery
- cloudflare-workers - 签名URL生成与边缘文件服务
- cloudflare-pages - 集成R2绑定的Pages Functions
- cdn-setup - 资产分发的CDN配置