创建 R2 存储桶
登录 Cloudflare 控制台,左侧菜单找到 R2 → 创建存储桶:
- 存储桶名称:
blog-images(全局唯一,建议加前缀如zjunwei-blog-images) - 位置:选择「自动」(亚太用户数据会自动路由到就近节点)
创建完成后,你会看到存储桶的控制台页面。可以直接在这里上传文件,但我们的目标是自动化——用命令行批量管理。
用 wrangler 操作 R2
如果你还没装 wrangler(上篇 Workers 教程应该装过了):
npm install -g wrangler
wrangler login
创建一个专门管理 R2 的 Worker 项目(R2 需要绑定到 Worker 才能通过自定义域名访问):
mkdir r2-proxy && cd r2-proxy
wrangler init r2-proxy
在 wrangler.toml 中绑定刚刚建的 R2 桶:
name = "r2-proxy"
main = "src/index.js"
compatibility_date = "2025-01-01"
[[r2_buckets]]
binding = "IMAGES" # 代码里用这个名字引用
bucket_name = "zjunwei-blog-images"
编辑 src/index.js,让 Worker 代理 R2 的图片访问:
export default {
async fetch(request, env) {
const url = new URL(request.url);
const key = url.pathname.slice(1); // /photo.png → photo.png
if (!key) {
return new Response('R2 Image Proxy', { status: 200 });
}
// 从 R2 读取对象
const object = await env.IMAGES.get(key);
if (!object) {
return new Response('Not Found', { status: 404 });
}
// 设置合理的缓存头
const headers = new Headers();
object.writeHttpMetadata(headers);
headers.set('cache-control', 'public, max-age=31536000, immutable');
headers.set('etag', object.httpEtag);
return new Response(object.body, { headers });
},
};
部署:
wrangler deploy
绑定自定义域名
部署完 Worker 后,默认域名是 r2-proxy.你的子域.workers.dev。我们需要绑定自己的域名,比如 img.zjunwei.com。
前提:域名已接入 Cloudflare DNS。
在 Cloudflare 控制台 → Workers 和 Pages → r2-proxy → 触发器 → 自定义域,添加 img.zjunwei.com。
等 DNS 生效后,访问 https://img.zjunwei.com/photo.png 就能直接从 R2 拉图片了。
上传图片:命令行批量操作
R2 兼容 S3 API,所以任何 S3 工具都能用。推荐用 awscli 或 rclone:
方式一:awscli(轻量)
# 安装
pip install awscli
# 配置(R2 的 Access Key 在 Cloudflare 控制台 → R2 → 管理 R2 API 令牌 获取)
aws configure --profile r2
# AWS Access Key ID: 你的R2_Access_Key
# AWS secret Access Key: 你的R2_secret_Key
# Default region name: auto
上传单张图片:
aws s3 cp ./screenshot.png s3://zjunwei-blog-images/ \
--endpoint-url https://你的AccountID.r2.cloudflarestorage.com \
--profile r2
批量上传整个目录:
aws s3 sync ./local-images/ s3://zjunwei-blog-images/ \
--endpoint-url https://你的AccountID.r2.cloudflarestorage.com \
--profile r2
方式二:rclone(功能更全)
# 安装
curl https://rclone.org/install.sh | sudo bash
# 配置(交互式,选 S3 类型,填 R2 的 endpoint 和密钥)
rclone config
配置好后,上传和同步就很简单了:
rclone copy ./local-images/ r2:zjunwei-blog-images/
博客图片自动压缩 + 上传脚本
每次截图后手动压缩再上传太麻烦。写一个脚本,一键完成:
#!/bin/bash
# upload-image.sh - 图片压缩并上传到 R2
BUCKET="zjunwei-blog-images"
ENDPOINT="https://你的AccountID.r2.cloudflarestorage.com"
PROFILE="r2"
MAX_WIDTH="1200" # 博客内容区宽度,再大没意义
for img in "$@"; do
filename=$(basename "$img")
ext="${filename##*.}"
# 如果不是 WebP,转换成 WebP(体积平均减少 60%)
if [ "$ext" != "webp" ]; then
newname="${filename%.*}.webp"
convert "$img" -resize "${MAX_WIDTH}x" -quality 85 "$newname" 2>/dev/null || {
echo "ImageMagick 未安装,跳过压缩"
cp "$img" "$newname"
}
else
newname="$img"
fi
echo "上传: $newname"
aws s3 cp "./$newname" "s3://$BUCKET/" \
--endpoint-url "$ENDPOINT" \
--profile "$PROFILE" \
--content-type "image/webp"
echo "链接: https://img.zjunwei.com/$newname"
done
用法:
# 装 ImageMagick(macOS)
brew install imagemagick
# 一键上传
chmod +x upload-image.sh
./upload-image.sh screenshot1.png cover.jpg diagram.png
在 Typecho 中使用 R2 图片
Typecho 编辑器里直接粘贴 R2 链接就行:

如果你的文章里引用服务器本地的图片太多,写个批量替换脚本:
# 把文章里 /usr/uploads/ 的图片路径替换为 R2 链接
sed -i 's|/usr/uploads/|https://img.zjunwei.com/|g' your-article.md
更进一步:在 Typecho 主题里改附件上传逻辑,上传后自动同步到 R2,然后本地删除。不过这需要改 PHP 代码,取决于你对主题的定制深度。
安全与防盗链
R2 默认是公开访问的(通过 Worker 代理),如果想限制:
方案一:Token 鉴权(简单)
在 Worker 里加 token 校验:
export default {
async fetch(request, env) {
const url = new URL(request.url);
const key = url.pathname.slice(1);
// 只允许带有效 token 的请求
const token = url.searchParams.get('token');
if (token !== '你的密钥') {
return new Response('Forbidden', { status: 403 });
}
const object = await env.IMAGES.get(key);
if (!object) return new Response('Not Found', { status: 404 });
const headers = new Headers();
object.writeHttpMetadata(headers);
headers.set('cache-control', 'public, max-age=31536000');
return new Response(object.body, { headers });
},
};
图片链接变成:https://img.zjunwei.com/photo.webp?token=xxx
方案二:Referer 防盗链
用 Cloudflare WAF 规则,只允许来自 zjunwei.com 的 Referer 访问图片 Worker 的路由。在 Cloudflare 控制台 → 安全性 → WAF → 自定义规则 里设置。
一个月能省下什么
假设博客日均 3000 PV,每页 10 张图,每张 150KB:
| 项目 | 服务器直出 | R2 方案 |
|---|---|---|
| 月度外网流量 | ~135GB | 0(R2 出站免费) |
| 服务器带宽压力 | VPS 跑满 | 0(Cloudflare 扛) |
| CDN 回源流量费 | 看 CDN 厂商 | 0(自带 CDN) |
| 图片加载速度 | 看 VPS 位置 | 全球 300+ 节点,50ms |
如果 VPS 带宽是 1Mbps 小水管,135GB 流量能把服务器拖垮。换成 R2 后,图片请求全部走 Cloudflare 边缘节点,你的 VPS 只处理 PHP 动态请求,负载直降 90%。
Q&A
Q:R2 和普通的图床(如 SM.MS、imgur)比有什么优势?
自建可控,不怕图床跑路。而且 R2 免费 + 不跑外网流量,普通图床要么限流量、要么限容量、要么塞广告。唯一的代价是前期配置需要 10 分钟。
Q:R2 上传慢怎么办?
用 aws s3 sync 或 rclone 都是多线程上传,几百张图几分钟搞定。日常写文章一张张传,几乎感觉不到延迟。
Q:Typecho 能不能直接把附件存到 R2?
可以,装 Typecho 插件「COS for Typecho」或类似 S3 兼容插件,把 endpoint 指向 R2 就行。不过 R2 的 S3 兼容需要设置 AWS_REGION=auto 和正确的 endpoint URL。
Q:10GB 用完了怎么办?
超出后 $0.015/GB/月。10GB 以外,每多存 1GB 一个月多花一毛五。对于个人博客,10GB 可能这辈子都用不完。
服务器的硬盘和带宽是有限的,但 Cloudflare 的 300 个边缘节点不是。把图片交给 R2,你的 VPS 只专心跑好 PHP——各司其职,才是正经架构。