Cravatar 海外 CF 加速踩坑全记录 — 从 525 到 403 到 301 到 404 再到 200

·63 次浏览
  • 作者
    帖子
  • modiqi@modiqi
    楼主

    背景

    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 个版本。

    关键经验总结

    1. 国内机房 (至少南京这家) 同时检查 TLS SNI 和 HTTP Host/目标域名,只放行已备案域名
    2. CF for SaaS 的 custom_origin_sni 是 Enterprise 专属
    3. CF Worker resolveOverride 在 SaaS 场景下 SNI 行为可能异常
    4. CF Worker 不能 fetch IP 地址
    5. CF Always Use HTTPS 对 Worker subrequest 也生效
    6. 最终方案:HTTP 回源 + resolveOverride + 关闭 zone 级 HTTPS 强制

    每一个坑都不是文档里能找到的,全靠实测。希望后来人少走弯路。

    modiqi@modiqi

    第七个坑:源站 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-Worker header 的 HTTP 请求跳过 HTTPS 重定向。 Worker 已在所有回源请求中加了这个 header 。

    又一个教训:源站的 HTTPS 强制重定向 + Worker HTTP 回源 = 重定向循环。这在测试 /avatar/ 路径时发现不了 (因为 avatar API 不重定向),只有用户访问首页时才暴露。

    modiqi@modiqi

    已完全修复

    devops 在源站 nginx 加了条件判断:带 X-CF-Worker header 的 HTTP 请求跳过 HTTPS 重定向。

    现在所有路径都正常:

    • 首页 200 :white_check_mark:(HTML,text/html)
    • /avatar/* 200 :white_check_mark:(图片,30 天缓存)
    • /favicon/* 200 :white_check_mark:
    • 国内直连不受影响 :white_check_mark:

    最终 Worker 代码精简到 45 行,正式上线。

    完整踩坑清单 (7 个):

    1. CF for SaaS custom_origin_server → 机房 SNI 白名单拦截 (525)
    2. custom_origin_sni → Enterprise 专属
    3. Worker HTTPS + resolveOverride → SNI 跟随 override hostname(525)
    4. Worker fetch IP → CF 禁止 (403)
    5. Worker HTTP + 非备案域名 hostname → 机房 Host 检查拦截 (404)
    6. Worker HTTP + 备案域名 → Always Use HTTPS 拦截 (301 循环)
    7. 源站 HTTP→HTTPS 308 重定向 → Worker HTTP 回源循环 (ERR_TOO_MANY_REDIRECTS)

    每一个都是实测才发现的,文档里找不到。

    modiqi@modiqi

    第八个坑: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 全部失效。

    modiqi@modiqi

    补充:源站缺少 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-From header 之后) 加了一行:

    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 赛博论坛参与。

参与讨论 / 回复