我的博客所有图片、视频都存在对象存储里。最开始用的是 Cloudflare R2,但 R2 开通公共访问/绑定域名那一步要求验证信用卡,而我没有能用于验证的信用卡——桶虽然能建成私有的,可私有桶没法直接给访客看图。
折腾下来找到一个完全免信用卡的替代方案:Backblaze B2 私有桶 + Worker 服务端签名代理。桶全程私有,不开放任何公共访问,访客通过博客自己的接口看图;个人小站量级基本零成本。这篇把配置过程和踩的坑都记下来。
适用场景:跑在 Cloudflare Workers(或任何能做服务端签名的后端)上的个人站点,想白嫖对象存储、又不想公开桶、也没有信用卡。
一、为什么选 B2
- 10 GB 免费存储额度,个人博客图片够用很久;
- 出口流量有免费额度(每日免费额度 + 与 Cloudflare 的 Bandwidth Alliance 联盟),个人小站的图片代理流量基本碰不到收费线;
- S3 兼容 API,意味着可以用标准的 AWS Signature V4 签名访问,各种语言/运行时都能对接;
- 桶可以全程私有,靠后端签名访问,不需要把文件暴露成公开 URL,也不需要信用卡验证。
整体访问链路是这样的:
访客请求 jxe.me/media/b2/uploads/images/xxx.jpg
│
▼
Cloudflare Worker(用存在后台的 B2 密钥做 AWS Sig V4 签名)
│ 签名后的 S3 API 请求
▼
Backblaze B2 私有桶(不公开,无签名一律拒绝)
二、B2 侧配置(约 5 分钟)
1. 注册账号
打开 backblaze.com 注册,邮箱验证即可,不需要绑卡。
2. 创建存储桶(Bucket)
进入 B2 Cloud Storage → Buckets → Create a Bucket:
- Bucket Unique Name:全局唯一名字,例如
my-moments-bucket; - Files in Bucket are:选 Private(这是本方案的核心,全程私有);
- Object Lock 和默认加密保持关闭/默认即可;
- 记下桶所在区域,例如
us-west-004。

3. 创建 Application Key
进入 Account → Application Keys → Add a New Application Key:
- Name of Key:随便取,如
moments-worker; - Allow access to Bucket(s):只选上一步创建的那个桶(遵循最小权限,不要选全部桶);
- Type of Access:Read and Write;
- 过期时间留空(长期有效)。
创建成功后页面只显示 一次 密钥,立刻保存好这两样东西:
- keyID(Application Key ID)
- applicationKey(Application Key)
关掉弹窗就再也看不到完整 applicationKey 了,丢了只能重建。
4. 拿到 S3 兼容端点
B2 同时提供原生 API 和 S3 兼容端点,本方案用 S3 兼容端点,格式固定:
https://s3.<区域>.backblazeb2.com
比如桶在 us-west-004,端点就是:
https://s3.us-west-004.backblazeb2.com
在桶详情页也能直接看到这个端点,复制即可。
三、博客后台配置
我的博客后台「存储管理」里支持 R2 / B2 切换,填上一步拿到的信息:
| 后台字段 | 填写内容 | 示例 |
|---|---|---|
| 上传存储 | 选择 B2 | — |
| B2 S3 兼容端点 | 上一步的端点 | https://s3.us-west-004.backblazeb2.com |
| B2 桶名 | 桶的 Unique Name | my-moments-bucket |
| B2 Key ID | 申请到的 keyID | — |
| B2 Application Key | 申请到的 applicationKey | — |
| B2 公开访问域名 | 私有桶留空 | 留空 |
最后一项解释一下:如果桶设成了公开并绑了自定义域名,可以填域名走直连(更快、不消耗 Worker 请求);本方案桶是私有的,这里什么都不填,系统会自动改走 Worker 签名代理。

点「测试连接」:后台会向桶里写入一个临时标记对象、读取校验后再删除。三项全部成功就说明端点、桶名、密钥都正确。
四、它在背后是怎么工作的
理解原理有助于以后排错:
- 新文件统一加
b2/前缀。切换存储后,新上传的图片/视频/头像/评论图写入 B2,对象 key 形如b2/uploads/images/2026-10-07/xxx.jpg; - 老文件原地不动。R2 里的历史文件没有
b2/前缀,继续走 R2 域名访问,切换存储不需要迁移任何数据; - 访问时按前缀分流。输出图片地址时识别
b2/前缀:B2 配置了公开域名就直连,没配(私有桶)就输出/media/b2/...代理地址; - Worker 代签。请求到 Worker 后,用后台的 keyID / applicationKey 按 AWS Signature V4 规则算出签名,带着签名去 B2 的 S3 端点取对象返回给访客;
- B2 只认签名,任何人直接访问桶地址都会被拒绝,所以桶保持私有也不影响看图。
五、踩坑记录
坑 1:端点漏掉 https://
S3 端点要求填完整地址。复制时如果只粘了 s3.us-west-004.backblazeb2.com,签名请求会直接失败。后台已做了防呆处理(自动补 https://),但其他自己写代码对接的场景记得自己保证协议头完整。
坑 2:Key 权限给成了整个账号
第一次创建 Application Key 图省事选了访问全部桶,后来才改成「仅指定桶」。建议一开始就按最小权限来:Key 万一从后台泄露,影响面也只限这一个桶。
坑 3:切换 B2 后相册「同步不到图片」
切换存储后第一次点「后台相册同步」,列表是空的。排查发现同步逻辑在识别图片来源时只认 R2 的路径,没认 B2 对象的 b2/ 前缀,导致 B2 里的图被当成未知来源跳过。
修复思路是做统一的 key 归一化:不管图片来自 R2 域名、/media/ 代理路径还是 B2(b2/uploads/...),都先归一成同一个内部 key 再比对和入库。这个改动后来还演进出了全站媒体「只存 key、输出时实时拼域名」的结构——换 CDN 或换存储时,数据一行都不用改。
[!tip]
经验:凡是涉及多存储后端的系统,入库时一定要有一层「来源 URL → 统一 key」的归一化函数,否则每加一个后端就要在所有业务逻辑里打补丁。
坑 4:私有桶直接复制对象链接打不开
这不是故障。私有桶里对象的 URL 必须带签名参数(X-Amz-Signature 等),裸链接打开返回 403 是正常表现。正确的访问方式只有两种:签名代理(本方案),或者把桶/对象改成公开。
六、成本与验证
- 存储:10 GB 免费额度内;
- 出口:小站图片代理流量很小,在 B2 免费出口额度内基本零成本;流量主要由 Cloudflare 边缘缓存承担(热点图只回源一次);
- 验证方法:
- 后台测试连接三项通过;
- 前台上传一张图片,确认能正常显示;
- 打开图片地址确认走的是
/media/b2/...代理路径; - B2 控制台桶文件列表里能看到带
b2/前缀的对象。
至此,一个没有信用卡、桶全程私有、历史文件零迁移的博客图片存储方案就跑起来了。配合 EdgeOne 给 R2 域名做国内加速(见另一篇《用腾讯 EdgeOne 给 Cloudflare R2 套一层国内 CDN》),免费额度内就能同时搞定「存得下」和「打开快」。

评论6