
出门在外,用 Cloudflare 保护好自己
一个新网站刚上线,最先出现的访客往往不是读者,而是扫描 .env、寻找 WordPress 后台和尝试 SSH 登录的自动程序。当然,检查结果一般不通知你。
很多人最初接触 Cloudflare,只是把网站的 DNS 记录设为已代理(Proxied)。继续翻控制台才会发现,它管的远不止网站入口:Email Routing 处理域名邮件转发,Tunnel 接入海外托管的自建应用,Page Shield 监控第三方脚本,R2 可以存备份,Notifications 负责告警。缓存、Pages 和 Workers 还能分担一部分源站工作。
下面就按实际使用顺序来:先说访问端,再看海外自建应用的入口,最后处理缓存、静态托管和故障切换。说到底,无非是管好请求从哪里进、谁能进,以及哪些请求根本不必回源。
一、安全入口 WARP
公共 Wi-Fi 的问题不只在于密码简单。你通常不知道网络由谁维护,也不知道同一网段里有哪些设备。未加密的 DNS 查询会暴露查询内容,明文 HTTP 流量可以被直接读取,同一网段中的设备也可能互相扫描。因此,公共网络不该被当成可信环境。
Cloudflare 提供 1.1.1.1 公共 DNS;1.1.1.2 增加恶意域名过滤,1.1.1.3 再增加成人内容过滤。手机和浏览器也可以使用 DoH(基于 HTTPS 的 DNS,DNS over HTTPS)加密 DNS 查询。不过,在中国大陆不建议使用。
WARP(现归入 Cloudflare One Client)会通过 WireGuard 或 MASQUE 把设备流量送到 Cloudflare,再由 Gateway 执行 DNS、网络和 HTTP 策略。机制本身不复杂,麻烦的是中国大陆的实际网络环境并不配合。
二、自建域名邮箱
邮箱往往比服务器更值得优先保护,因为许多账号都通过邮件重置密码。邮箱失守,其他账号也可能受到牵连。
自己有一个域名,就不必把邮箱全部押在别人给的地址上。Cloudflare 的 Email Routing 可以把域名邮箱转发到现有收件箱:把 MX 记录指向 Cloudflare,再配置路由规则和目的地即可。它只转发、不存邮件,所以不能替代收件箱。catch-all 用起来很方便,垃圾邮件也会跟着一起进来。
有了域名邮箱,每个服务可以给一个独立地址(比如 github@example.com),哪个服务把地址卖给了垃圾邮件,一眼就能看出来。子地址(user+tag@example.com)也一样好用。
收信只是其中一部分,还要防止别人冒用你的域名发送邮件。这主要依靠三类 DNS TXT 记录:
- SPF(发件人策略框架,Sender Policy Framework):声明哪些服务器有权用你的域名发信;
- DKIM(域密钥识别邮件,DomainKeys Identified Mail):给发出的邮件签名,收件方可以验证内容没有被改过;
- DMARC(基于域名的消息认证、报告与一致性,Domain-based Message Authentication, Reporting, and Conformance):告诉收件方 SPF/DKIM 失败时怎么处理,并要求把结果报告回来。
个人常见的情况是「只收不发」。很多服务商会建议把 SPF 收紧到已知的发信来源,DMARC 则先设为 none。Cloudflare 的 DMARC Management 要求域名 DNS 托管在 Cloudflare,它会接收聚合报告,列出谁在使用这个域名发信、通过率如何。先观察一段时间,确认合法来源没有漏网,再逐步把策略收紧到 quarantine 甚至 reject。上来就一刀切,重要邮件被拒收就有趣了。
三、代理状态、HTTPS 和几条有用的规则
Cloudflare 代理开启后,访客先连接 Cloudflare,再由它决定是否把请求转发给源站。
DNS 记录设为已代理(Proxied)后,受支持的 HTTP/HTTPS 流量会先经过 Cloudflare;设为仅 DNS(DNS only)时,Cloudflare 只做 DNS 应答,访客拿到真实的源站 IP 后直接访问。这个区别很重要,后面的 WAF、缓存和规则都建立在请求确实经过 Cloudflare 的前提上。
DDoS 防护先替入口承受和过滤一部分攻击流量,WAF 再检查 Web 请求里的异常特征。不过,一个格式完全正常、却会让数据库全表扫描的搜索请求,可能仍然会让你的服务器满载,所以应用自己还得限制查询和资源消耗。
接入以后,至少要处理好 HTTPS、WAF 和高成本接口这三件事。
第一,HTTPS 两端都要顾到。浏览器到 Cloudflare、Cloudflare 到源站是两段独立连接,常规部署优先使用 Full (strict),别让浏览器这边挂着小锁,回源时却还在走明文。HSTS 可以强制浏览器只用 HTTPS 访问域名,最低 TLS 版本也可以按需收紧。HSTS 生效后很难临时退回 HTTP,开启前记得确认范围内的所有子域都已支持 HTTPS。
第二,让 WAF 处理明显异常的请求。托管规则覆盖常见攻击特征,自定义规则则可以加入具体的业务约束。比如网站完全没有使用 WordPress,可以添加:
(http.host eq "blog.example.com" and http.request.uri.path contains "/wp-admin")动作选 Block。配置文件和仓库目录也一样,/config、/.git 这类路径本来就不该对公网有任何回应。
第三,把普通浏览流量和高成本接口分开处理。连续阅读十篇博客很正常,同一来源一分钟调用几十次短信接口却值得限制。可以先按 IP 设置限速规则,例如对 /api/send-sms 设置「同一 IP 每 10 秒超过 20 次就质询或返回 429」。具体阈值必须根据真实业务量调整。
托管 WAF、Super Bot Fight Mode 和限速规则可以搭配使用,其中 Super Bot Fight Mode 在 Free 套餐中不可用。不过,别把「拦得多」当成唯一成绩。搜索引擎、监控和支付回调也是自动化流量,误杀它们一样会让你抓狂。
浏览器页面可以接受挑战,机器接口则应优先使用签名、身份校验和明确的限流响应。支付回调、监控等正常的自动请求不适合交互式挑战。
评论、注册和联系表单可以加 Turnstile,但后端一定要调用 SiteVerify 验证 token。只在页面里摆一个验证组件,绕过前端的请求照样能进来。
限速也要有分工,边缘按 IP 挡住一批滥用,应用继续按账号、订单或 API Key 做限速。
这些配置只保护经过 Cloudflare 的请求。如果源站 IP 已经暴露,攻击者仍可能绕过 Cloudflare 直接访问源站。
四、DNS 与源站
先别急着往源站防火墙里堆规则,DNS 这边也有两项容易漏掉的配置。
DNSSEC 会给 DNS 记录加签名,用来抵御伪造响应和缓存投毒。控制台开启后,还要把生成的 DS 记录提交给域名注册商;只点完 Cloudflare 里的开关没用。
CAA 记录则声明哪些证书颁发机构 (CA) 可以为你的域名签发证书,对于普通人而言意义大于实用性,且更在意这个的其实是操作系统、浏览器厂商。
再来看源站。如果 IP 已经暴露,应该立即更换 IP,来不及的情况下可以补上两层限制:
第一层是让防火墙只允许 Cloudflare IP 回源。把源站入站规则收紧到 Cloudflare 的 IP 段,普通直连请求便没有入口。麻烦在于这份列表需要持续同步;规则一旦过期,你可能先把正常回源关在门外,然后为了恢复服务又临时开出一扇更大的窗。
第二层是 Authenticated Origin Pulls。它要求回源连接出示证书,通过双向 TLS(mTLS)验证来访者;证书不对,源站直接拒绝。默认的全局证书只能证明「流量来自 Cloudflare 网络」。如果想严格到「来自你的账号」,需要上传自己的证书做 Zone 级校验。它也不适用于 Tunnel:走 Tunnel 的回源本来就不开放公网入口,没有必要再套一层。
另一种做法是直接取消公网入口,改用 Tunnel。
五、Tunnel 运行在你服务器上的反向代理
假设 VPS 上托管着这些服务:
| 服务 | Docker 网络里的地址 | 想使用的入口 |
|---|---|---|
| 个人博客 | http://blog:8080 | blog.example.com |
| Grafana | http://grafana:3000 | grafana.example.com |
| 文件管理 | http://files:8080 | files.example.com |
传统做法是用 Nginx、Caddy 或 Traefik 按域名反向代理,再自行处理端口映射、证书、公网入口和源站防护。对于只想访问几个内部服务的个人用户,这套网关也会带来额外维护。
在简单场景中,Cloudflare Tunnel 可以省掉其中一部分工作。
5.1 直接接入私网服务
在能访问这些服务的位置运行 cloudflared,由它主动连接 Cloudflare。随后把公网 hostname 映射到内部地址,请求就能沿隧道送进来。
它的价值不只是隐藏 IP,而是让自建应用干脆取消公网直连入口。即使海外服务器本身有公网 IP,也可以关掉这些应用的公网端口,只保留 cloudflared 所需的出站连接。
本地管理 Tunnel 时,路由部分可以长这样:
ingress:
- hostname: blog.example.com
service: http://blog:8080
- hostname: grafana.example.com
service: http://grafana:3000
- hostname: files.example.com
service: http://files:8080
- service: http_status:404这只是 Tunnel 配置里的路由片段,不包含 Tunnel 身份和 DNS 配置。使用控制台管理时,填写等价的 hostname 与 service 映射即可。
让 cloudflared 和目标容器加入同一个可互通的 Docker 网络,就能直接通过服务名访问。应用通常不需要再写 ports: "3000:3000",流量在容器网络里走完就好,没必要先绕到宿主机公网端口再回来。
有两个小白问题很常见:容器里的 localhost 是它自己,不是隔壁容器;不同 Compose 项目默认也不在同一个网络,要显式安排网络连接。目标应用还得监听可被该网络访问的容器接口,不能只监听它自己内部的回环地址。
5.2 简单的反向代理规则
在简单的按域名转发到不同服务的场景里,Tunnel 可以省掉专门维护一层 Nginx 的工作,同时也支持一些简单的前缀匹配。
如果原来的 Nginx 还承担复杂重写、特殊缓冲、本地负载均衡或其他业务逻辑,可以继续保留,让 Tunnel 接在它前面。两者并不冲突。
这样也不再需要为这层入口维护 Cloudflare 回源 IP 白名单或证书校验,因为 Tunnel 使用主动建立的出站连接。
Tunnel 不会自动删除旧的端口映射。如果启用隧道后仍保留公网 :3000,服务依然可以绕过隧道直接访问。
5.3 能访问和允许访问,是两回事
公开博客可以直接对公网开放;Grafana、文件管理和内部工具,最好在入口加 Access,让指定身份登录后再访问。
图中是职责示意,不代表 Cloudflare 内部所有安全阶段的精确顺序。
Tunnel 负责连通,Access 负责入口身份,应用负责登录后的权限。不能因为后台地址不容易猜,就省略这些控制。
访问这类海外自建应用并不要求设备开启 WARP。Grafana 等 Web 管理界面可以直接使用 Access 做身份验证,再通过 Tunnel 访问内部服务;这样也避免把第 1 节提到的不稳定设备隧道变成管理入口的必要条件。
还有一个顺手的玩法:把本地开发服务临时给同事看,或者接收开发环境的 Webhook。不必先买台服务器部署一遍。但开发调试器、数据库控制台和带真实凭据的后台,不应该随着一个临时链接一起公开。
5.4 别再跟 SSH 端口斗智斗勇
Cloudflare 的标准代理不支持原生 SSH。把 SSH 从 22 改到 443 也没用,换了端口,里面跑的仍然是 SSH。
使用 Tunnel 和 Access,可以把 SSH 也收进身份入口后面。常见做法是在本机装 cloudflared,然后给 SSH 配置一个代理命令:
Host my-server
HostName ssh.example.com
User ubuntu
ProxyCommand cloudflared access ssh --hostname %h之后仍然是熟悉的 ssh my-server,只是连接前多了一次 Access 身份验证。服务端 SSH 密钥认证和主机密钥校验照常保留。先把新通道和应急通道都试通,再关闭旧的公网入口。
如果 cloudflared 跑在容器里,连接宿主机 SSH 时同样不能随手填 localhost:22,要填它实际可达的地址。
配置完成后,运行这些应用的海外 VPS 只需发布必要入口,不再暴露每个应用的公网端口。
六、Page Shield 盯住从第三方脚本
网站入口之外,还要留意页面加载的第三方脚本。统计、客服、广告和支付组件都由外部供应商提供,却运行在你的页面中。第三方服务被攻破后,攻击者可能注入恶意脚本,窃取访客输入或支付信息;网页盗刷(Magecart)就是这类攻击。由于相关服务器不归网站所有,这条供应链很容易被忽略。
Page Shield(现在叫 Client-side security)会记下页面实际加载了哪些脚本,并在出现新脚本、内容变化或已知恶意脚本时告警。Free 套餐不能监控连接和 Cookie,也不能用 CSP 规则直接拦截清单之外的资源。
Page Shield 主要负责发现变化,不会自动判断每个新脚本是否合理。能删除的第三方脚本应尽量删除;必须加载的脚本可以用 SRI 固定哈希,并为关键页面配置 CSP。供应商是否可信,仍需自行评估。
七、数据与备份:R2 和「别把鸡蛋放一个篮子」
入口守住了,数据也不能只放在原处。硬盘损坏、误删和勒索加密都绕得过 WAF,最后还是得靠备份。
Cloudflare R2 是兼容 S3 的对象存储,而且不收取互联网出口流量费。rclone、restic 等工具都支持 S3 协议,用来定期加密并上传海外自建应用的数据很顺手。
但是有一个宏观层面的取舍:域名、DNS、CDN、网站、存储、备份如果全在 Cloudflare 一家,账号出问题的那天,这些会一起失联。R2 适合做日常备份,但真正关键的数据,值得再找一家独立的存储兜底。把服务交给平台,可以减少自己的维护范围,不能把所有恢复办法也一起交出去。
八、人在外面,也要收得到消息
防护和备份都配好了,还差一件很实际的事:出问题时,得有人告诉你。
Cloudflare Notifications 可以把安全事件、WAF 触发、证书到期和用量超限推给你。Webhook 在 Free 套餐中不可用;如果要接 Telegram、飞书这类平时更常看的渠道,需要留意这一点。需要流量数据的通知只适用于已代理(Proxied)的记录,仅 DNS(DNS only)的记录没有对应的 HTTP/HTTPS 流量可报。
想主动知道服务是否还活着,可以用 Health Checks。Cloudflare 节点会定期探测源站,并在故障时尽快通知。这个功能在 Free 套餐中不可用。
告警最好发到自己平时真的会看的地方。邮件容易被其他消息淹没,Webhook 到聊天工具通常更及时;阈值也要亲手触发一次,免得配置半年从没响过。
九、Cache:源站最擅长处理的请求,是根本没收到的请求
防护和缓存分别属于安全与性能,但都会减少到达源站的请求。对于一个月才更新一次的文章,没有必要在每次访问时都查询数据库、重新渲染模板。
9.1 先区分三件事:能缓存、缓存多久、按什么区分
Cloudflare 默认不会缓存所有 HTML 和 JSON。把 DNS 记录设为已代理,也不代表首页从此就不再回源。
为传统源站配置缓存,先回答三个问题:
| 问题 | 例子 |
|---|---|
| 这份内容能否共享? | 博客正文通常可以,订单详情通常不行 |
| 允许旧多久? | 图片可以很久,公开行情可能只能几秒 |
| 什么变化会让内容不同? | 文章 ID、语言、尺寸、租户、登录身份 |
Cache Rules 负责把这些判断落到路径、条件和缓存设置上。例如给 (http.host eq "blog.example.com" and starts_with(http.request.uri.path, "/articles/")) 设置缓存资格(Eligible for cache),Edge TTL 设成 6 小时,文章正文就不必每次回源。排查时先看响应头里的 cf-cache-status:HIT 是命中,MISS 是回源,比盯着控制台猜规则有没有生效快得多。
不要因为功能名是 Cache Everything,就直接缓存全站。个人中心、订单等私有页面一旦错误共享,可能把一个用户的内容返回给另一个用户。
9.2 浏览器缓存和边缘缓存,可以各过各的日子
对于完全公开的页面,可以让浏览器短存,Cloudflare 存久一点,例如:
Cache-Control: public, max-age=60, s-maxage=600浏览器存 60 秒,共享缓存存 600 秒。HTML 还要配合缓存资格设置,并确认没有被其他规则覆盖。这样更新内容时,可以主动清掉边缘副本,不必让访客一直看到旧页面。
带哈希的静态文件则简单得多:文件变了,名字跟着变。app.a1b2.js 换成 app.c3d4.js,旧文件放心长缓存,新版本使用新地址。浏览器不会因为你清了 Cloudflare 缓存,就主动忘掉它本地的旧文件。
9.3 Cache Key 处理不好,会出现两种相反的问题
第一种是把本来相同的内容拆成一大堆副本。
/photo.jpg?utm_source=a
/photo.jpg?utm_source=b
/photo.jpg?utm_source=c如果统计参数不影响图片,它们没必要各占一份缓存。
第二种更危险:把本来不同的内容错误地合并。
/photo.jpg?width=320
/photo.jpg?width=1920如果忽略全部查询参数,两种尺寸就可能错误地共用缓存。多租户服务要是连租户标识也忽略,问题就不只是图片大小不对,还可能变成跨租户数据泄露。
因此可以清理确定无关的参数,不能把参数一锅端。签名、权限、版本和内容选择参数,要特别小心。
9.4 全球有缓存,不等于全球是一块内存
一个地区刚访问过资源,另一个地区不一定立刻本地命中。边缘节点各自承接流量,缓存有冷热,也有淘汰。
Tiered Cache 会让下层节点在没有命中时先去找上层缓存,尽量避免每个地区都单独回源。对于读者分散、源站又比较远的博客和图片站,这通常比继续从应用里挤几毫秒更划算。
如果允许短暂使用旧内容,还可以在支持的缓存路径上使用 stale-while-revalidate:先把可接受的旧版本给访客,后台再更新。适合文章、公开目录,不适合所有业务数据。
Cache Response Rules 可以在拿到源站响应后、写入缓存前,根据响应调整缓存指令。它不只是改一改最后发给浏览器的响应头,而是真的会参与缓存决策。
对于私有页面,删除 Set-Cookie 并不会消除正文中的用户数据,不能据此把响应当成公开内容缓存。
十、WebP 和 Polish 让图片减肥,别让访客替你扛原图
如果页面的 HTML 只有几 KB,图片却有数十 MB,优先压缩图片通常比微调 JavaScript 更有效。
Polish 会自动压缩经过 Cloudflare 缓存的图片,并在合适时提供 WebP。源站可以继续保留 JPEG、PNG,外部 URL 也不必全部重写。这个功能在 Free 套餐中不可用。
10.1 文件名没变,不代表图片没变
请求 photo.jpg,返回的实际内容可以是 WebP。浏览器是否支持、转换后是否足够划算,才是决定因素;看响应里的 Content-Type,不要只看地址栏。
「开启 WebP 后为什么还有 JPEG」未必是故障。有些图片转完反而更大,或者画质损失不值得,Polish 会保留更合适的结果。Lossless 模式也不会把 JPEG 强行做成有损 WebP。
10.2 它和缓存是搭档,不是两个互不相干的开关
Polish 是在缓存图片上工作的。如果图片绕过缓存、开发模式还开着,或者旧缓存尚未过期,看不到效果也不奇怪。开发模式还会同时暂停边缘缓存和 Polish。
排查时,一起看 CF-Cache-Status、Content-Type 和 Cf-Polished,比对着控制台反复开关有用。
10.3 想要缩略图,光换格式还不够
4000 像素的照片转成 WebP,再放进 300 像素的卡片里,依然是在让访客下载一张没必要那么大的图片。
需要裁剪、缩放、不同质量和自动格式选择时,再接 Images transformations;前端配合 srcset、合适的尺寸和懒加载。Polish 负责常规减重,Images 负责更完整的图片处理,两者不是一回事。
处理到这里,又会冒出一个很现实的问题:手里有好几个网站,难道每个都要重新搭一套图片入口?先把问题留着,后面用 SaaS 和 Snippets 一起收拾。
十一、Pages 静态网站托管
Pages 不只是一个上传 HTML 的地方。它会托管静态文件和发布产物,再通过 Cloudflare 的分发与缓存体系提供服务。这样就不必让一台位于固定地区的服务器承担所有缓存未命中的回源请求。
11.1 普通 CDN 与 Pages,源头就不一样
| 方式 | 文件主要由谁托管 | 某地缓存没命中后 |
|---|---|---|
| VPS 加普通 CDN | 你自己的源站 | 根据缓存层次,最终可能回到你的 VPS |
| Pages 静态部署 | Cloudflare 托管的部署产物 | 由 Pages 的分层缓存与托管存储链路取得 |
| 自写 Worker 代理文件 | 取决于脚本访问哪里 | 可能仍去远方的服务器或对象存储 |
Pages 上传的静态资源会自动使用 Tiered Cache,不需要给自定义域名单独再打开一次。大多数情况下也不用额外覆盖 Pages 的缓存逻辑,否则发布以后可能还在提供旧文件,外层缓存也可能抢在重定向和 Functions 之前返回。
这不是「多了一层 CDN」那么简单,而是你不再需要自己维护静态站点的那一端。
11.2 全球分发
可以理解为,你的静态网站进入了 Cloudflare 的全球资源分发体系,各地访问都由这套体系承接。
但「每个边缘节点在第一次访问前就已经完整预存全部文件,而且永不淘汰」,不是应该向读者承诺的行为。分层缓存意味着有本地命中,也可能有上层取回与底层存储读取。
实际收益是这些读取由平台处理,新地区的访客不必直接访问自建源站。
所以,博客、文档、作品集、前端静态资源放到 Pages 后,原来的 VPS 就算重启,已经部署的静态内容也不再依赖它继续开机。CMS 如果只参与构建,挂了可能影响下一次发布,而不会直接让上一版文章跟着消失。
这改变了站点对自建源站的依赖,比单次测速分数更值得关注。
11.3 动态部分单独处理,不必把整站拖回动态链路
Pages 的静态文件当然不会自己查询数据库,不过项目可以使用 Pages Functions,借助 Workers 运行时处理表单、鉴权、中间件和 API。动态部分也可以单独放到 Worker,不必和静态页面绑在一起。
比如博客正文是静态内容,评论走 API,搜索可以用静态索引,管理后台藏在 Access 后面。
没有必要为了显示实时访问量,就让整篇文章在每次请求时重新渲染。把动态功能拆成独立接口即可。
11.4 Workers 对比
Workers 默认同样在全球边缘执行;启用 Smart Placement 等放置策略后,有些计算会转到更靠近后端的位置。所以「全球部署」「这次代码在哪里执行」「数据在哪里」「缓存是否跨节点复用」,其实是四个不同的问题。
尤其别把手写 Worker 里的 caches.default 当成 Pages 的整套分发系统。Cache API 主要操作当前数据中心缓存,不会自动使用 Tiered Cache。
不过,现在的 Workers Static Assets 也能托管静态资源,并使用分层缓存。静态资源直接响应时可以就近提供;通过脚本绑定获取时,则要结合脚本执行位置来理解。没必要为了突出 Pages,就把 Workers 说成只有单点缓存的小服务器。
Pages 提供了完整的静态网站托管流程,Workers 则更适合需要较多应用逻辑的项目。现有 Pages 项目没有必要仅因工具趋势而迁移,新项目可以根据官方当前方向和自身需求评估 Workers。
十二、Workers 动态能力
有了静态页面,通常还想要一点动态能力:联系表单、订阅接口、登录校验、第三方数据查询,或者把几个接口合成一个。
这些很适合 Workers。一个最小骨架长这样:
export default {
async fetch(request, env) {
const url = new URL(request.url)
const token = request.headers.get('Authorization')
if (token !== `Bearer ${env.API_TOKEN}`)
return new Response('unauthorized', { status: 401 })
return fetch(`https://api.example.com${url.pathname}`)
},
}它做的事情只有两件:校验环境变量里的 token,然后转发请求。这只是骨架,生产环境还要补上方法限制、超时和错误处理,却已经足以说明 Workers 的核心价值:先在边缘管住入口,再决定要不要惊动后端。
一个常见组合是:前端照常由静态资源系统提供,只有 /api/ 进入 Worker;Worker 先验证请求和身份,再决定是否访问数据库、邮件服务或其他 API。这样即使动态部分出错,也可以让正文和导航继续可用。反过来,如果首页每次都要等待五个外部接口,Worker 本身离用户很近也无法消除下游请求的延迟。
12.1 地理位置要跟数据一起看
Worker 靠近用户,不代表数据库也靠近用户。一次很轻的数据库查询,计算只用一毫秒,网络往返可能花得更多。
所以还得看具体业务:代码应该靠近用户还是靠近数据,公开结果能不能缓存,几次往返能不能合并,非关键功能是否可以从首屏拆走。Smart Placement 优化的是整次请求,并不执着于让代码离访客最近。
12.2 缓存也有不同摆法
在 Worker 里查询 Cache API,是代码先执行,再看有没有缓存。Workers Cache 则把关系倒过来,把分层缓存放在 Worker 入口前面。在 wrangler 里用一行 "cache": { "enabled": true } 开启,缓存资格仍由 Cache-Control 等响应头决定。命中时 Worker 不运行,未命中才执行代码。这里最容易踩的是 Vary:它会参与缓存键,如果取值不受控制,缓存变体很快就会膨胀。
这与单个节点的 Cache API 不是同一套机制。缓存位置变了,执行次数、刷新方式和计费边界也会跟着变,还是要按实际请求路径判断。
12.3 先管住入口,再保管密钥
把第三方 API Key 藏进 Worker 很有用,但接口本身仍然要做权限和用量限制。
否则密钥虽然没有泄露,别人仍可以通过你的 Worker 无限调用付费接口。
同理,不要写一个接收任意 url 然后直接 fetch(url) 的接口,除非你真的准备认真设计代理的安全边界。固定服务白名单、请求大小、超时和下游成本,比「Serverless 自动扩容」更值得先想一想。
如果只是修改少量路由或选择后端,不一定需要完整的 Worker 项目,也可以考虑 Snippets。
十三、Snippets 加 SaaS:多个域名,共用一套图片服务
Snippets 不仅能修改响应头,还能用少量边缘代码决定请求去哪里、以什么地址取资源,以及是否根据响应切换后端。用它来改源站、改路径,或者根据响应换一条路,都很合适。
下面拿多个图片域名举例。先说限制:Snippets 在 Free 套餐中不可用。
13.1 域名可以各用各的,后面的服务统一管理
假设你有博客、相册和几个小项目,希望它们分别使用自己的图片域名:
img.alpha.example
img.beta.example
img.gamma.example它们背后的需求其实一样:HTTPS、缓存、图片压缩、WebP、回源,以及出问题时的处理。
Cloudflare for SaaS 可以把这些域名注册为某个服务 Zone 的 Custom Hostnames,再通过 CNAME 接入统一的服务。它原本是为客户自定义域名准备的,拿来整理自己维护的多个项目也很合适。
如果服务所在的 Zone 已经启用图片优化,接进来的图片请求就可以在符合条件的链路上使用这里配置的缓存和 Polish。
这里复用的是图片服务本身的能力,不是给接入域名凭空增加一整套功能。只添加一条 CNAME 也不够,Custom Hostname、域名验证、证书和服务入口都要配好。
13.2 SaaS 负责接入,Snippet 负责路由
多个域名接入以后,还要为它们配置各自的资源地址。
对于统一图片源,可以设计这样一张路由表:
| 访客使用的域名与路径 | Snippet 映射后的资源地址 |
|---|---|
img.alpha.example/photo.jpg | assets-origin.example.com/alpha/photo.jpg |
img.beta.example/photo.jpg | assets-origin.example.com/beta/photo.jpg |
img.gamma.example/photo.jpg | assets-origin.example.com/gamma/photo.jpg |
assets-origin.example.com 是你自己管理、启用相应缓存和图片优化的资源入口,背后可以是自己的服务器,也可以是第 7 节的 R2——零出口费让图片源放 R2 很合适。这里只提供公开图片,后端存储按项目划分目录。
Snippet 根据允许的 Host 查表,改写取资源的 URL,然后发起请求。用户仍然使用自己的图片域名,源站存储和优化策略集中维护。
这是根据 SaaS 的接入能力与 Snippets 的路由能力组合出的设计示意,不是所有请求处理阶段的精确时序,也没有在本文示例域名上做过线上验证。
如果只是「一个域名对应一个源站」,SaaS 的 Custom Origin 本身就能处理,没必要为了用 Snippets 而增加脚本。需要按路径分流、增加项目目录、统一改写或加入 fallback 时,Snippet 才派得上用场。
13.3 按顺序验证接入链路
实际配置时,可以按下面的顺序逐段验证链路:
- 在服务 Zone 启用 SaaS,配置可用的默认源站和接入 hostname。
- 添加各项目的 Custom Hostname,完成域名与证书验证,再安排 DNS 切换。
- 先验证「不加自定义脚本也能正确访问」,别把证书、DNS、路由三个问题同时调试。
- 为外部图片 hostname 配置 Snippet 匹配条件,映射到固定的资源源站和项目路径。
- 在真正取图的链路上配置缓存与 Polish;确认缓存资格、源站响应头和内容协商正常。
- 分别从各个外部域名请求同一组测试图片,检查内容、响应头、缓存命中和清缓存结果。
路由脚本要限定允许的 Host 和目标源站。不要接受任意 URL 并直接转发,否则图片服务可能被滥用为公共代理。
另外,取图目标不能再次匹配原来的 Snippet 路由,否则会形成递归请求。
13.4 多域名复用能力,不等于自动合并所有缓存
alpha/photo.jpg 与 beta/photo.jpg,即使文件名相同,也必须保持隔离。统一入口应该保留项目维度;涉及签名、版本和尺寸的参数,同样不能随手删除。
反过来,如果多个域名确实提供完全相同、公开且允许分发的文件,可以设计统一的规范资源 URL,减少重复。但这件事要明确规划,不能看到它们在同一个 SaaS Zone,就默认所有域名会共享同一份缓存。
如果客户域名的 DNS 记录也设为已代理(Proxied),还会涉及 O2O 流量:请求先经过客户 Zone,再进入 SaaS 提供方的 Zone。客户侧的一些设置可能覆盖或干扰服务侧设置。比如客户侧绕过了缓存,就别再期待 Polish 仍按原计划工作。排错时要看完整链路,不能只盯着服务 Zone 上的开关。
这套组合的好处是,更换图片存储、调整压缩方式或增加备用源时,项目中的图片链接可以保持不变,存储与处理策略也能集中维护。
十四、用 Snippets 实现边缘 fallback
如果主源站暂时不可用,可以让边缘尝试访问备用来源。
把故障切换逻辑只写在源站中存在一个问题:源站不可用时,其中的补救代码也可能无法执行。边缘位于这个故障域之外,仍有机会选择备用源。
14.1 先把两个名字相似的东西分开
Cloudflare for SaaS 的 fallback origin,指的是未指定其他源站时使用的默认回源地址。
它名字里有 fallback,但并不因此具备「主站失败后自动切备用站」的含义。
这里要做的是另一件事:Snippet 发出主请求,看看结果,再决定是否访问备用源。
14.2 最适合从公开只读资源开始
这类兜底最适合从公开、只读的资源开始,例如:
- 主图片存储出错,尝试同路径的备用图片存储。
- 博客动态源站故障,尝试事先部署在 Pages 的静态文章快照。
- 非关键的公开数据源暂时失败,返回仍可接受的静态版本。
- 实在没有可用内容,返回一张自己设计的维护页面,而不是默认报错屏幕。
逻辑并不复杂,可以先写成这样:
只处理明确允许的公开 GET / HEAD 请求
尝试主源站
正常响应:返回
选定的 502 / 503 / 504 或可捕获的网络异常:尝试备用源
其他响应:保留原来的语义
备用源有有效内容:返回备用内容
两个源都不可用:返回 503 维护页,并禁止长时间缓存落到 Snippets 的代码骨架,长这样:
const PRIMARY = 'https://origin.example.com'
const BACKUP = 'https://backup.example.com'
export default {
async fetch(request) {
const url = new URL(request.url)
let response = await fetch(PRIMARY + url.pathname + url.search)
if (response.status === 502 || response.status === 503 || response.status === 504)
response = await fetch(BACKUP + url.pathname + url.search)
return response
},
}这是去掉所有护栏后的骨架:没有限制 GET/HEAD、没有校验 Host、没有超时。护栏比主流程更重要,接下来几节全是护栏。
14.3 只对明确的失败做切换
不要把所有非 200 响应都视为故障。401、403 可能是在保护数据,404 可能表示资源已经删除。切换后端不应改变这些响应的含义。
也不要随意重试写操作。下单请求超时并不代表主站没有处理成功,如果备用站再次执行,就可能产生重复订单。建议先从 GET、HEAD 和明确的公开路径开始。
身份信息也不应直接转发给备用源。备用静态站通常不需要用户 Cookie、Authorization 或源站内部凭据,构造备用请求时只传递必要信息。
不要把维护页当成正常页面长期缓存。等价的公开副本可以按正常内容返回;如果只是维护提示,应保留 503 等故障语义,并采用短缓存或 no-store,避免主站恢复后仍继续返回旧的故障页。
14.4 边缘 fallback 能兜底,但它不会自动变成负载均衡器
这种 fallback 通常是被动的:请求发现主站失败后,才访问备用站。如果每次都等待主站超时,最终即使成功返回,延迟也可能无法接受。
因此要设合理的等待预算。需要持续健康检查、自动避开坏节点、全局状态和更复杂流量调度时,再考虑 Load Balancing,或者设计相应的 Worker 与状态服务。
Snippet 的子请求数有上限,重定向链也会占用次数。主源和备用源各请求一次,留给其他接口的空间可能就不多了,设计时要把这部分算进去。
它也只能处理脚本能够观察到的失败:DNS、TLS、前置拦截、整个边缘平台故障,都可能让请求根本到不了这段逻辑。
备用内容必须提前准备,并尽量与主源隔离故障域。如果主备都在同一台主机的两个容器里,整机断电时仍会同时不可用。
十五、Snippets 的其他用途
除了多域名路由和 fallback,Snippets 还可以处理几种短小但烦人的边缘逻辑。
资源迁移时,可以把旧图片路径映射到新存储,让外部链接保持不变。这样不必在迁移开始前就替换数据库中的所有历史链接。
规范化公开资源地址时,可以去掉确定不影响内容的统计参数,统一等价路径,减少缓存碎片。但必须保留签名、租户、尺寸和版本等影响响应内容的参数。
灰度与备用路径也适合放在这里,让明确选中的请求走新服务,其他请求维持原状。但分流结果会影响内容时,缓存必须相应区分,不能让第一位访客替全世界抽签决定版本。
对于适用的下载入口,Snippet 还可以做轻量签名校验,检查签名与有效期后再提供内容。签名应在访问受保护内容之前生效,而不是把返回链接藏得长一点。需要可靠的全局次数限制、复杂密钥管理或状态时,就该换用更合适的应用层方案。
这些场景的共同点是逻辑短、状态少,而且主要围绕请求和响应。需要数据库、持久会话、队列或复杂后端时,就该换 Workers 了。Snippet 虽小,放对地方却能省掉一层自建中间服务。
十六、剩下的加速开关,按收益慢慢加
主线搭好以后,再看这些补充功能会容易很多:
| 功能 | 场景 | 说明 |
|---|---|---|
| HTTP/3 | 移动网络 | 注意 UDP 劣化 |
| Early Hints | HTML 返回前,关键资源已经能确定 | 不是把全站资源都提前下载 |
| Speed Brain | 站内连续阅读 | 主要帮助后续导航,不负责首次打开 |
| APO | WordPress 页面反复动态生成 | 会员页、购物车仍要考虑个性化 |
| Tiered Cache / Cache Reserve | 冷门资源经常重新回源 | 缓存不是备份,TTL 也不保证一直驻留 |
| Argo Smart Routing | 动态请求主要慢在回源网络 | 只能优化路由,不等于获得一条专线 |
至于旧教程里那些「全部打开」的加速开关,最好先看日期。Mirage 已经停用,Auto Minify 已弃用;Rocket Loader 还在,但它会修改脚本加载方式,框架、登录和支付组件都要认真测试。
同理,压缩文本、正确设置浏览器缓存、减少第三方脚本,往往比折腾几个花哨参数更有收益。把一个没有必要的统计 SDK 删掉,有时就是当天最有效的性能优化——第 6 节的 Page Shield,正好能帮你数清楚页面上还剩几个这样的 SDK。
结语
Cloudflare 控制台里的功能很多,但没必要一次开完。先解决首要问题,再确认新功能由谁维护、出了故障如何绕过。十几个开关连在一起,很快就会变成一套需要长期照看的架构。
不过,上线前还是值得花十分钟检查几件事:图片从几个外部域名访问是否正确,登录内容有没有串缓存,备用源是否真的独立,旧端口是否已经关闭,Access 出问题时自己还能不能通过应急通道进去,备份上一次成功是什么时候。
测速度也别只在自己电脑上刷新两次。要分冷缓存与热缓存,换地区、换网络,看看真实读者到底在哪里等。普通 Cloudflare 网络也不等于中国大陆专线,节点多不代表每条线路都合适。
最后别漏掉账号多因素认证、最小权限 Token 和备份。把服务交给平台可以缩小自己的维护范围,但恢复手段仍应掌握在自己手里。
