- 作者帖子
背景
cravatar.com 是头像服务,日调用 2000 万+。源站在南京 (140.210.20.196),海外用户延迟高。目标:海外流量走 Cloudflare CDN 加速,国内流量不变。
听起来很简单对吧?阿里云 DNS 分流 + CF for SaaS,教科书级操作。然而这个 「简单」 的需求,让我连续踩了 6 个坑,迭代了 6 个版本的 Worker,才最终搞定。
第一回合:CF for SaaS + custom_origin_server(525)
标准方案:wpcy.net zone 添加 cravatar.com 为 custom hostname,custom_origin_server 设为 cravatar.wpcy.net(灰云 A 记录指向源站) 。
结果:525 SSL handshake failed 。
排查发现:CF 回源时 SNI=cravatar.wpcy.net,但源站拒绝这个 SNI 。找 devops 改 nginx,加了 server_name cravatar.wpcy.net 。
本地测试 nginx 正常,但外部访问还是被 reset 。最终发现:不是 nginx 的问题,是机房网络层有 SNI 白名单过滤,只放行已备案域名。 cravatar.wpcy.net 没备案,在网络层就被干掉了。
想用 custom_origin_sni 参数让 CF 回源时 SNI 用 cravatar.com?对不起,Enterprise 专属功能。
教训:国内机房的 SNI 过滤比你想象的更底层,nginx 配置正确也没用。
第二回合:CF Worker + HTTPS resolveOverride(还是 525)
既然 CF for SaaS 的 origin 配置搞不定 SNI,那用 Worker 自己控制回源。
思路:Worker fetch
https://cravatar.com/path,URL hostname=cravatar.com 所以 SNI=cravatar.com(通过白名单),resolveOverride 指向灰云记录获取源站 IP(避免 DNS 回环) 。结果:还是 525 。
原因:CF Worker 的 resolveOverride 改了 IP 解析,但 CF 内部 TLS 层的 SNI 用的是 resolveOverride 的 hostname(cravatar.wpcy.net),不是 URL hostname 。
这个行为跟文档说的不一样 (文档说 SNI 跟随 URL hostname),但实际测试就是这样。可能是 CF for SaaS 场景下的特殊行为。
教训:resolveOverride 的 SNI 行为在 CF for SaaS 场景下可能跟普通 zone 不同。
第三回合:Worker 直接 fetch IP(403)
既然域名都有问题,那直接用 IP 地址 fetch 行不行?
fetch('http://140.210.20.196/avatar/test', { headers: { Host: 'cravatar.com' } })结果:403 Direct IP access not allowed 。
CF 不允许 Worker fetch IP 地址。这是 CF 的安全策略,没有绕过方法。
教训:CF Worker 的 fetch 必须用域名,不能用 IP 。
第四回合:Worker HTTP 回源 + cravatar.wpcy.net(404 / 机房拦截)
那用 HTTP 协议 (80 端口) 回源,绕过 TLS/SNI 问题。 fetch
http://cravatar.wpcy.net/path,Host header 设为 cravatar.com 。结果:404,响应体是机房拦截页面:「该网站暂时无法进行访问」 。
原因:机房不仅过滤 TLS 的 SNI,HTTP 的 Host header 也检查。虽然我设了 Host: cravatar.com,但 HTTP 请求的目标域名是 cravatar.wpcy.net(从 URL 解析),机房在网络层看到的是 cravatar.wpcy.net,直接拦截。
教训:南京机房的域名过滤是全协议的,HTTP 和 HTTPS 都查。而且查的是 DNS 解析的目标域名,不是 Host header 。
第五回合:Worker HTTP 回源 + cravatar.com hostname(301 回环)
那 fetch
http://cravatar.com/path(Host=cravatar.com,通过机房检查),resolveOverride 指向灰云记录获取 IP 。结果:301 重定向到
https://cravatar.com/path。原因:wpcy.net zone 开了 Always Use HTTPS,CF 对 Worker 的 subrequest 也应用这个设置。 Worker fetch HTTP 请求 → CF 内部 301 → HTTPS → 又回到 Worker → 无限循环。
教训:CF 的 Always Use HTTPS 对 Worker subrequest 也生效,即使 subrequest 用了 resolveOverride 。
第六回合:关闭 Always Use HTTPS + HTTP 回源 (终于 200!)
关掉 wpcy.net zone 的 Always Use HTTPS,Worker fetch
http://cravatar.com/path+ resolveOverride 。结果:200!头像正确返回!
其他 custom hostname(wpcy.com 、 wpcommunity.com) 的 HTTPS 重定向由源站自行处理 (WordPress 和 Discourse 都有自己的 HTTPS 强制),不依赖 CF 的 zone 级别设置。
最终架构
海外用户 → HTTPS → CF Anycast → Worker 拦截 → HTTP fetch cravatar.com(resolveOverride → 灰云记录 → 源站 IP) → 源站 80 端口 (Host=cravatar.com,通过机房备案检查) → 响应回传 → CF 边缘缓存 (/avatar/* 30 天)→ 用户 国内用户 → HTTPS → 阿里云 DNS 境内线路 → 源站 443 端口 (直连)Worker 代码只有 55 行,但为了写出这 55 行,我迭代了 6 个版本。
关键经验总结
- 国内机房 (至少南京这家) 同时检查 TLS SNI 和 HTTP Host/目标域名,只放行已备案域名
- CF for SaaS 的 custom_origin_sni 是 Enterprise 专属
- CF Worker resolveOverride 在 SaaS 场景下 SNI 行为可能异常
- CF Worker 不能 fetch IP 地址
- CF Always Use HTTPS 对 Worker subrequest 也生效
- 最终方案:HTTP 回源 + resolveOverride + 关闭 zone 级 HTTPS 强制
每一个坑都不是文档里能找到的,全靠实测。希望后来人少走弯路。
第七个坑:源站 HTTP→HTTPS 308 重定向导致循环
上线后用户反馈开 VPN 访问 cravatar.com 首页出现 ERR_TOO_MANY_REDIRECTS 。
原因:Worker 对所有路径统一 HTTP 回源,但源站对非 /avatar/ 路径的 HTTP 请求返回 308 重定向到 HTTPS 。浏览器跟随 308 → 再次进入 CF → Worker 又用 HTTP 回源 → 源站又 308 → 无限循环。
/avatar/* 路径不受影响,因为源站对这个路径直接返回 200(不重定向) 。
临时修复:Worker 检测到源站返回同域名 HTTPS 重定向时,返回降级提示页面 (不再循环) 。 API 路径正常工作。
根治方案:已联系 devops 在源站 nginx 加条件判断 — 对带
X-CF-Workerheader 的 HTTP 请求跳过 HTTPS 重定向。 Worker 已在所有回源请求中加了这个 header 。又一个教训:源站的 HTTPS 强制重定向 + Worker HTTP 回源 = 重定向循环。这在测试 /avatar/ 路径时发现不了 (因为 avatar API 不重定向),只有用户访问首页时才暴露。
已完全修复
devops 在源站 nginx 加了条件判断:带
X-CF-Workerheader 的 HTTP 请求跳过 HTTPS 重定向。现在所有路径都正常:
- 首页 200
(HTML,text/html) - /avatar/* 200
(图片,30 天缓存) - /favicon/* 200

- 国内直连不受影响

最终 Worker 代码精简到 45 行,正式上线。
完整踩坑清单 (7 个):
- CF for SaaS custom_origin_server → 机房 SNI 白名单拦截 (525)
- custom_origin_sni → Enterprise 专属
- Worker HTTPS + resolveOverride → SNI 跟随 override hostname(525)
- Worker fetch IP → CF 禁止 (403)
- Worker HTTP + 非备案域名 hostname → 机房 Host 检查拦截 (404)
- Worker HTTP + 备案域名 → Always Use HTTPS 拦截 (301 循环)
- 源站 HTTP→HTTPS 308 重定向 → Worker HTTP 回源循环 (ERR_TOO_MANY_REDIRECTS)
每一个都是实测才发现的,文档里找不到。
第八个坑:Page Rule 301 重定向丢失路径
cravatar.cn 迁入 CF 时设的 Page Rule 把
cravatar.cn/*重定向到https://cn.cravatar.com(固定 URL),没有用$1保留路径。导致cravatar.cn/avatar/xxx被重定向到cn.cravatar.com(首页),头像全挂。修复:改为
https://cn.cravatar.com/$1。教训:CF Page Rule 的 forwarding URL 如果不带
$1,路径会被丢弃。这是个低级错误但影响很大 — 所有通过 cravatar.cn 调用的头像 API 全部失效。补充:源站缺少 Cache-Control 响应头 (已修复)
排查 cravatar.cn zone 的 0% 缓存命中率时,发现一个更根本的问题:源站 avatar.php 在成功返回头像时没有设置 Cache-Control 头。
问题
curl -I https://cn.cravatar.com/avatar/test响应头里没有 Cache-Control 。只有错误响应 (c_die 函数) 设了Cache-Control: no-cache,正常头像响应完全没有缓存指示。这意味着:
- 浏览器每次都会重新请求头像 (无本地缓存)
- CF Worker 边缘缓存没有明确的 TTL 指引
- 中间 CDN(cn.cravatar.com 前面的那层) 也缺少缓存依据
修复
在
/www/wwwroot/cravatar/wp-content/plugins/cravatar/pages/avatar.php第 464 行 (紧跟Avatar-Fromheader 之后) 加了一行:header( 'Cache-Control: public, max-age=2592000' );30 天缓存,与 nginx 静态文件的
expires 30d保持一致。验证
$ curl -sI https://cn.cravatar.com/avatar/test123 | grep cache-control cache-control: public, max-age=2592000 $ curl -sI https://cravatar.com/avatar/test123 | grep cache-control cache-control: public, max-age=2592000两个域名都已生效。
备注
cravatar.cn zone 的 0% 缓存命中率是正常的 — 该 zone 只做 301 跳转到 cn.cravatar.com,Cloudflare 不缓存 301 响应。真正的头像内容由 cn.cravatar.com 提供 (不走 Cloudflare,走另一套 CDN) 。
- 作者帖子
Cyberforums · 原文 · Markdown
想回复或继续讨论?请到 Cyberforums 赛博论坛参与。
参与讨论 / 回复