cloudflare-r2

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Cloudflare 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
    wrangler login
    or
    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
undefined
bash
undefined

Create 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
undefined
npx wrangler r2 bucket delete old-bucket
undefined

Object Operations

对象操作

bash
undefined
bash
undefined

Upload 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"
npx wrangler r2 object put app-assets/data/report.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
undefined
npx wrangler r2 object head app-assets/images/logo.png
undefined

S3-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令牌

  1. Go to R2 > Manage R2 API Tokens > Create API token.
  2. Select permissions: Object Read & Write, or Object Read only.
  3. Scope to specific buckets if possible.
  4. Save the Access Key ID and Secret Access Key.
  1. 前往R2 > 管理R2 API令牌 > 创建API令牌
  2. 选择权限:对象读写,或仅对象读取。
  3. 如有可能,限定到特定存储桶。
  4. 保存访问密钥ID和秘密访问密钥。

AWS CLI Configuration

AWS CLI配置

bash
undefined
bash
undefined

Configure 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
aws s3 sync ./dist s3://app-assets/static/
--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
aws s3 cp ./backup.tar.gz s3://app-assets/backups/backup-$(date +%Y%m%d).tar.gz
--endpoint-url=$R2_ENDPOINT
--profile=r2

List objects with prefix

列出带前缀的对象

aws s3 ls s3://app-assets/images/
--endpoint-url=$R2_ENDPOINT
--profile=r2
aws s3 ls s3://app-assets/images/
--endpoint-url=$R2_ENDPOINT
--profile=r2

Remove objects by prefix

删除带前缀的对象

aws s3 rm s3://app-assets/tmp/ --recursive
--endpoint-url=$R2_ENDPOINT
--profile=r2
undefined
aws s3 rm s3://app-assets/tmp/ --recursive
--endpoint-url=$R2_ENDPOINT
--profile=r2
undefined

Python 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")
undefined
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")
undefined

Worker 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
undefined
toml
undefined

wrangler.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"
undefined
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"
undefined

Worker 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.
  1. Go to R2 > bucket > Settings > Public access.
  2. Enable and set a custom domain (e.g.,
    assets.example.com
    ).
  3. Objects are accessible at
    https://assets.example.com/<key>
    .
bash
undefined
启用公开访问,无需Worker即可直接提供文件服务。
  1. 前往R2 > 存储桶 > 设置 > 公开访问
  2. 启用并设置自定义域名(例如
    assets.example.com
    )。
  3. 对象可通过
    https://assets.example.com/<key>
    访问。
bash
undefined

Or 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>

undefined
undefined

Lifecycle Rules

生命周期规则

Configure automatic object expiration or transition.
bash
undefined
配置自动对象过期或转换规则。
bash
undefined

Set 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 } } } ] }'
undefined
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 } } } ] }'
undefined

CORS Configuration

CORS配置

bash
undefined
bash
undefined

Set 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 } ] }'
undefined
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 } ] }'
undefined

Troubleshooting

故障排查

SymptomCauseFix
NoSuchBucket
error via S3 API
Wrong endpoint or bucket nameVerify endpoint is
https://<ACCOUNT_ID>.r2.cloudflarestorage.com
SignatureDoesNotMatch
Incorrect secret key or endpoint mismatchRegenerate R2 API token; ensure region is
auto
Uploads succeed but GET returns 404Key path mismatch (leading slash)R2 keys should not start with
/
Slow uploads for large filesSingle-stream uploadUse multipart upload; set
--expected-size
with wrangler
CORS errors in browserMissing CORS config on bucketAdd CORS rules for your origin domain
Worker binding returns
undefined
wrangler.toml
binding name mismatch
Verify
binding
name matches
Env
interface property
Public access returns 403Public access not enabledEnable in bucket Settings > Public access
症状原因解决方法
通过S3 API访问时出现
NoSuchBucket
错误
端点或存储桶名称错误验证端点为
https://<ACCOUNT_ID>.r2.cloudflarestorage.com
SignatureDoesNotMatch
错误
秘密密钥错误或端点不匹配重新生成R2 API令牌;确保区域设置为
auto
上传成功但GET请求返回404密钥路径不匹配(开头带斜杠)R2密钥不应以
/
开头
大文件上传缓慢单流上传使用分块上传;在wrangler中设置
--expected-size
浏览器中出现CORS错误存储桶缺少CORS配置为你的源域名添加CORS规则
Worker绑定返回
undefined
wrangler.toml
绑定名称不匹配
验证
binding
名称与
Env
接口属性一致
公开访问返回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配置