为“cloudflare-os”正名:这不是一个系统发行版,而是一套边缘优先的部署姿态
“cloudflare-os”这个代号,是我最近半年折腾自己那几台对外提供服务的机器时,一步一步沉淀下来的基础设施范式。刚起这个名字的时候,我确实觉得它应该是一个定制过的 Linux 发行版,或者某套一键初始化脚本;但做着做着才发现,真正值得沉淀的其实是另一件事:把服务器上那些重复、易错、又必须手工维护的“边缘相关”环节——证书、回源鉴权、真实客户端 IP、缓存语义、访问控制——全部变成一套可以被复制、被审计、被快速部署的模板,让每一台新机器一出生就具备“贴着 Cloudflare 边缘运行”的正确姿势。
这个工程适合谁?适合那些已经在用 Cloudflare 做 CDN 和 WAF 前置,但源站依然直连公网、每次排查真实 IP 都靠猜、缓存命中率常年上不去的站长和运维。它不是要替代你的 Linux 发行版,而是要把 Cloudflare 的“边缘能力”真正焊进系统配置里。这篇文章就把我的设计思路、踩坑过程和最终落地的配置完整写出来,当作一份可以直接抄作业的笔记。
1. 先搞清楚“cloudflare-os”到底要解决什么问题
我最初犯过一个典型错误:以为挂了 Cloudflare 之后,源站可以把安全责任全部甩给边缘。实际跑起来才发现,边缘只是入口,源站该面对的问题一个都没少,反而多出了几类新问题:日志里看到的客户端 IP 全是 Cloudflare 的节点 IP;边缘证书和源站证书之间的信任关系没建立好,回源链路堂堂正正地变成了裸 HTTP;源站返回的缓存头没有语义,导致 CDN 缓存形同虚设;云防火墙只开了 80/443,但还是被扫、被撞、被刷。
所以“cloudflare-os”在我这里的定义,不是一个可下载的 ISO,而是一套“面向 Cloudflare 边缘运行”的系统状态模板。它的目标非常具体:任何一台新机器,跑完这套模板之后,都应当满足四个基本条件。
第一,源站不直接暴露在公网流量里,至少要让 Cloudflare 的防护能力真正生效,而不是仅仅做一个 DNS 解析记录。第二,服务端日志可以还原出真实用户 IP、国家地区、请求命中和回源的完整链路。第三,边缘和源站之间的 HTTPS 信任关系是自动、可持续、不被浏览器证书搞混的。第四,缓存命中率可以被观察、被度量、被调整,而不是玄学。
我拿两台配置一样的服务器做过对比。一台按传统方式装好 nginx 后直接解析到 Cloudflare,另一台按 cloudflare-os 的模板调教。表面上看起来都是“一台服务器 + Cloudflare”,但压力上来之后差别非常大:传统那台的源站真实 IP 可以通过历史 DNS 记录和其他侧信道暴露,shodan 一扫就能看到,WAF 规则经常因为日志里全是伪造请求头而误判;模板这台则从系统防火墙就开始只信任 Cloudflare 的回源网段,日志、限流、证书全部围绕边缘链路设计,排查问题的时候不用再来回猜。
下面这个表可以直观看出两者差异:
| 对比维度 | 传统直连部署 | cloudflare-os 形态 |
|---|---|---|
| 对外暴露面 | 源站 80/443 直接可达 | 仅 Cloudflare 回源网段可达 |
| TLS 链路 | 边缘证书源站证书各管各 | 边缘到源站也走受信证书(Full strict) |
| 真实客户端 IP | 靠猜测和手改日志 | 恢复后直接落在访问日志 |
| 缓存控制 | 看边缘配置心情 | 由源站响应头显式声明 |
| 安全策略 | 边缘一套系统一套 | 边缘、防火墙、nginx 三层配合 |
这种“把边缘能力当作系统的一部分来设计”的思路,其实才是 cloudflare-os 的核心价值。它不是一个包,而是一种看待基础设施的方式。
2. 设计拆解:四个“内置”模块
2.1 边缘访问控制:安全决策要放得离攻击者更近
很多人的直觉是:Cloudflare 自带 WAF,源站系统防火墙随便开一下就行。这个想法在低流量阶段没错,但一旦有人针对源站真实 IP 发起绕过攻击,你就明白为什么系统层也要控制。
我在 cloudflare-os 里把访问控制拆成了两层。外层是 Cloudflare 的 WAF、速率限制和防火墙规则,负责拦截明显恶意的流量;内层是系统防火墙和 nginx 的访问控制,负责确保只有 Cloudflare 的回源网段能碰到源站端口。
为什么这么拆?因为边缘 WAF 依赖的是 HTTP 层信息,请求只要到了 Cloudflare 就能被检查;但系统防火墙处理的是网络层,如果源站把 80/443 对全世界开放,攻击者完全可以绕过 Cloudflare 的 DNS 解析直接访问源站 IP。只有系统防火墙同步限制来源网段,三层防线才真正闭合。
我见过不少生产事故,都是因为只配了 Cloudflare 的防护,忘了源站本身也开着 3306 或 22 端口。cloudflare-os 里第一条铁律就是:源站所有对外端口,默认只允许 Cloudflare 官方 IP 段来源访问,管理端口单独走受信 IP 白名单。
2.2 回源链路与证书信任:别让最后一段变成裸奔
Cloudflare 边缘终止了用户端的 TLS,但边缘到源站这一跳是独立的网络链路。如果你在 Cloudflare 控制台把 SSL/TLS 模式设成了 Full 或 Full strict,那源站必须也有一张受信任的证书;如果设成 Flexible,那边缘到源站就是明文 HTTP,等于用户数据出了 CDN 之后就裸奔。
我在早期犯过这个错:控制台选了这个模式又没在意,后来抓包看了一眼回源流量,HTTP 明文清晰可见,吓得立刻整改。
cloudflare-os 用的是 Full strict 模式,并搭配 Cloudflare 自家的 Origin CA 证书。这种证书的好处是:可以直接在 Cloudflare 控制台生成,不需要走公开 CA 的域名验证流程;有效期最长可达 15 年,省去 certbot 每三个月续期的折腾;只用于边缘到源站的回源链路,不会暴露给浏览器,因此即使私钥泄露,影响范围也远小于公网证书。
生成之后把证书和私钥放到源站,nginx 配置里指定好,再从 Cloudflare 控制台确认 SSL 模式是 Full strict,整个链路才算闭环。这个模块要让系统“默认信任边缘”,而不是“每次手动信任边缘”。
2.3 缓存语义:让源站决定什么能缓存
不少人觉得 CDN 缓存就是边缘自动帮你缓存静态文件,源站什么都不用管。实际情况是,边缘是否缓存某个资源,取决于源站响应头里的 Cache-Control、Expires、Vary 等字段。如果源站返回的响应头没有明确的缓存语义,即便你在 Cloudflare 页面里设置了缓存规则,也可能命中不了或者缓存了不该缓存的内容。
cloudflare-os 把缓存控制的权责划清:源站是内容的所有者,必须显式声明每个资源的缓存策略;边缘只是执行者,按照声明去缓存。
我常用的一套简单规则是:
- HTML 页面:
Cache-Control: no-cache或s-maxage=30,保障内容和动态更新的时效性; - 静态资源(CSS、JS、图片):
Cache-Control: public, max-age=86400,让边缘放心缓存一天; - API 接口:
Cache-Control: no-store,杜绝动态数据被错误缓存。
这里有个小知识点值得展开:s-maxage是专门给共享缓存(CDN)看的指令,max-age是给浏览器看的。如果你写Cache-Control: public, s-maxage=60, max-age=60,那 CDN 会把内容缓存 60 秒,浏览器也会缓存 60 秒;如果只想让 CDN 缓存而浏览器不缓存,可以写s-maxage=60, max-age=0。这个区分在做 202 Status、302 重定向、页面 HTML 的缓存控制时特别实用。
2.4 日志与可观测性:从日志里能读出一整条链路
传统的 nginx 访问日志,默认记录的是$remote_addr,但经过 Cloudflare 之后,这个字段取到的是 Cloudflare 边缘节点的 IP。如果不做特殊处理,你看到的所有访问都来自同一批 IP,根本无法定位真实用户,更谈不上按地区分析。
cloudflare-os 的日志模块做两件事:一是通过real_ip模块恢复真实客户端 IP;二是把 Cloudflare 自带的请求追踪 ID(CF-RAY)、用户所在国家(CF-IPCountry)、CDN 缓存状态(CF-Cache-Status)全部记录到日志里。
这样一条日志就能讲清楚完整故事:真实用户从哪个国家来,请求命中了 CDN 缓存还是回源了,走了哪个边缘节点,nginx 返回了什么状态码,耗时多少。排查线上问题的时候,这些字段的价值是无可替代的。我甚至有段时间靠 CF-Cache-Status 的分布比例,反推哪些页面该缓存而没缓存,优化完命中率立刻提升了将近一倍。
3. 实操:把一台裸 VPS 改造成“cloudflare-os”形态
3.1 初始环境准备
我用的基底是 Debian 12 minimal,原因是系统干净、稳定、运维资料多。如果你习惯 Ubuntu 或者 Rocky Linux,这套逻辑一样适用,只是包管理命令略有差异。
第一步,更新系统并安装必要组件:
apt update && apt upgrade -y apt install -y nginx ufw curl第二步,配置基础防火墙,先默认拒绝所有入站,再放行 ssh 和 80/443。这里注意:如果你已经知道管理用的固定 IP,就把 ssh 也限制成白名单;如果管理 IP 不固定,至少改成非默认端口并禁用密码登录,能减少大量扫描攻击。
ufw default deny incoming ufw allow 80/tcp ufw allow 443/tcp ufw allow from 你的管理IP to any port 22 ufw enable第三步,关闭不需要的系统服务、确认时间同步正常。很多人忽略时间同步,但 TLS 证书、日志排序、rate limit 全都依赖准确时间,跑 NTP 这一步别省。
timedatectl set-ntp true3.2 Cloudflare 控制台侧配置
这一步做的事情都在 Cloudflare 后台,但因为它直接决定了源站系统的行为,所以我把它们写进 cloudflare-os 的模板里。
第一,把需要接入的域名解析记录设为“代理状态”(橙色云),让流量先经过 Cloudflare。这一步是基础,但很关键:DNS 记录如果设成“仅 DNS”(灰色云),那相当于直接暴露源站 IP,之前的全部防护都白搭。
第二,确认 SSL/TLS 加密模式为 Full (strict)。这个模式要求源站也部署受信证书,正好对应我们在系统里装的 Origin CA 证书。在 Cloudflare 控制台一路找到 SSL/TLS 概览页面,把模式改成 Full strict,再把边缘证书设置成默认。
第三,生成 Origin CA 证书:在 Cloudflare 控制台的 SSL/TLS 源站服务器里,创建源站证书,选择包含的域名范围即可。生成的证书和私钥要妥善保管,私钥只放在源站服务器上。有效期为 15 年左右,基本一次性配置完就不用再担心短期续期。
证书文件:/etc/nginx/certs/cloudflare-origin.pem 私钥文件:/etc/nginx/certs/cloudflare-origin.key第四,顺手在 Cloudflare 侧设置几条基础的安全规则,比如开启 bot fight mode(看自己业务情况)、设置基础 WAF 规则、把后台管理路径加入速率限制。这些虽然严格来说不算系统侧配置,但和源站 nginx 的访问控制相互配合,才能形成立体防御。
3.3 nginx 回源配置
这是 cloudflare-os 的配置文件核心,我会把完整可用的 server 块配置贴出来,并解释每个部分在干什么。
先建立一个专门承接 Cloudflare 回源流量的 server 块:
log_format cf '$remote_addr - $remote_user [$time_local] ' '"$request" $status $body_bytes_sent ' '"$http_referer" "$http_user_agent" ' '"$http_cf_ray" "$http_cf_ipcountry" ' '"$upstream_cache_status"'; set_real_ip_from 173.245.48.0/20; set_real_ip_from 103.21.244.0/22; set_real_ip_from 103.22.200.0/22; set_real_ip_from 103.31.4.0/22; set_real_ip_from 141.101.64.0/18; set_real_ip_from 108.162.192.0/18; set_real_ip_from 190.93.240.0/20; set_real_ip_from 188.114.96.0/20; set_real_ip_from 197.234.240.0/22; set_real_ip_from 198.41.128.0/17; set_real_ip_from 162.158.0.0/15; set_real_ip_from 104.16.0.0/13; set_real_ip_from 104.24.0.0/14; set_real_ip_from 172.64.0.0/13; set_real_ip_from 131.0.72.0/22; real_ip_header CF-Connecting-IP; server { listen 443 ssl http2; server_name example.com; ssl_certificate /etc/nginx/certs/cloudflare-origin.pem; ssl_certificate_key /etc/nginx/certs/cloudflare-origin.key; ssl_protocols TLSv1.2 TLSv1.3; access_log /var/log/nginx/cf_access.log cf; root /var/www/html; index index.html; location / { try_files $uri $uri/ =404; } }这段配置里,set_real_ip_from把 Cloudflare 的回源网段列为可信来源,real_ip_header CF-Connecting-IP告诉 nginx 从哪个请求头读取真实 IP。之后日志里的$$remote_addr就会是真实用户 IP,而不是边缘节点 IP。
注意:完整网段列表会不定期更新,建议从 Cloudflare 官方 IP 列表接口拉取并定期同步,别把这份清单当成永久固定的。
如果源站还要承接动态接口,可以在同一个 server 块里加一个反向代理转给本机或内网应用,比如常见的 3000 端口 Node.js 服务:
location /api/ { proxy_pass http://127.0.0.1:3000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; }X-Forwarded-Proto这里要重点看一眼。Cloudflare 到源站如果走的是 HTTPS,这个头应该是 https;如果你的应用逻辑里用$_SERVER['HTTPS']判断协议,很可能因为源站接收的是 HTTPS 而出现“到处都是 http 链接”的鬼问题。真正合理的做法是在应用层只信任X-Forwarded-Proto,或者让 nginx 设置proxy_set_header X-Forwarded-Proto https固定为 https 并把请求转发到内网 http。
3.4 验证:用 curl 和日志确认整条链路
配置完之后不要急着收工,我强烈建议做三个验证动作。
第一,验证 TLS。用 openssl 测一下源站证书是否正常:
openssl s_client -connect 127.0.0.1:443 -servername example.com结果里会显示证书链,确认证书与域名匹配、有效期正常。此时从公网直接访问源站 IP 的 443 端口,如果防火墙生效,应该超时或拒连;只有通过 Cloudflare 访问才能正常进入源站。
第二,验证真实 IP 恢复。正常通过 Cloudflare 访问一次页面,然后看日志:
tail -n 5 /var/log/nginx/cf_access.log如果日志里的第一列是具体用户 IP,而不是 Cloudflare 的 104.x 或 172.x 段,说明真实 IP 恢复成功。如果你身在同一个局域网测试,也可以自己伪造CF-Connecting-IP头,但这时 nginx 会信任它,所以必须确保防火墙层面已经卡住了非 Cloudflare 来源,否则会有人直接构造这个头来伪装 IP。
第三,验证缓存状态。用 curl 带头部请求,观察响应里的 CF-Cache-Status:
curl -I https://example.com/assets/app.js第一次大概率是 DYNAMIC 或 MISS,第二次再请求同一个文件,如果配置了合理的缓存语义,应该变成 HIT,说明 CDN 已经把资源缓存住了。
4. 高频踩坑与排查技巧实录
这一节全部来自我实操里真实踩过的坑,每一个都对应一次半夜爬起来翻日志的经历。
我先整理一张速查表,再逐个展开说。
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 访问日志里全是 Cloudflare 节点 IP | 缺少 set_real_ip_from 或 real_ip_header 配置错误 | 补全可信网段,使用 CF-Connecting-IP |
| 浏览器提示证书错误但边缘正常 | 控制台 SSL 模式没选 Full strict | 改为 Full strict 并在源站部署 Origin CA 证书 |
| CDN 缓存命中率极低,全是 DYNAMIC | 源站响应头没有合理的 Cache-Control | 按内容类型设置 s-maxage/max-age |
| 被扫出源站真实 IP 且还能访问 | 防火墙没限制只允许 Cloudflare 网段 | ufw 或 iptables 只放行 CF 回源网段 |
| 动态接口被缓存返回旧数据 | API 响应头缺失 no-store | 给 API 显式声明 Cache-Control: no-store |
| curl 直接测源站总是 403 | ufw 已经拦截非 CF 来源 | 这是预期行为,通过 Cloudflare URL 访问验证 |
第一个坑,也是新手最容易踩的,是日志里看不到真实 IP。我当时把real_ip_header写成了X-Forwarded-For,结果在所有经过 Cloudflare 的请求里都能拆出好几层 IP,顺序还会变。后来才明白,Cloudflare 官方约定的是CF-Connecting-IP,它只存一个原始 IP,没有多级代理的拼接问题。这也是为什么 cloudflare-os 信任的请求头要统一用 CF-Connecting-IP,而不是自己瞎猜 X-Forwarded-For 的第几个值。
第二个坑,是缓存命中率上不去的排查思路。我发现很多动静态混合的站点,资源明明都是同一个路径,命中也一直是 DYNAMIC。后来在浏览器开发工具里看响应头,才意识到源站根本没有输出 Cache-Control;Cloudflare 把它当成不可预测的动态内容。于是我给静态文件统一加了缓存头,命中率立即提升。核心原则就是:一个资源能否缓存,不由 URL 后缀决定,而由响应头决定。
第三个坑,是 IP 伪造风险。虽然 nginx 配置好了 CF-Connecting-IP 信任,但那是建立在“只有 Cloudflare 能连到源站”的基础上。如果你没有把防火墙限制在 Cloudflare 网段,人家直接连源站 IP 并带上假的CF-Connecting-IP头,就能轻松伪造任意 IP,甚至可以绕过你的 IP 限流策略。所以,配置了 real_ip 之后,防火墙限制“只允许 CF 网段回源”不是可选项,是必选项。
第四个坑,是代理层传递 Host 头导致的跨域问题。有时候应用内生成的跳转链接域名不对,排查半天发现 nginx 里少了proxy_set_header Host $host。边缘收到的域名如果你的源站配置没有显式传给后端,后端拿到的是源站 IP 或者默认 server_name,生成出的绝对路径自然不对。这类问题在 cloudflare-os 里统一要求:凡是从 Cloudflare 来的流量,回源时 Host 头、X-Forwarded-Proto、X-Forwarded-For 必须显式设置。
第五个坑,是关于 WAF 误杀的。有些正常请求因为 User-Agent 过于冷门,被 Cloudflare 的托管规则拦掉。我当时一度以为是源站 nginx 配置问题,后来在 Cloudflare 的事件中心里看到被拦截的请求才明白。这也提醒我一件事:cloudflare-os 的部署不仅是服务器侧的事,边缘侧的规则同样需要纳入版本管理,方便回滚和审计。
5. 边界、扩展和我的最终体会
cloudflare-os 走到这一步,其实已经不再是一台服务器的配置了,它变成了一种基础设施的思考习惯。真正能延伸出去的方向非常多。
比如,当你有多台源站的时候,模板的意义进一步放大。以前我每加一台机器,都要手动检查证书、防火墙、日志格式这些细节;现在 cloudflare-os 的配置可以做到模块化,一份 nginx 模板加一份初始化脚本,新机器跑一遍就自动具备了正确的边缘协作逻辑,省下来的时间非常可观。
再比如,把 Cloudflare 的边缘规则也变成代码。它现在支持用 API 和配置管理工具去维护 DNS 记录、WAF 规则、缓存规则,相当于把整个边缘链路都纳入了基础设施即代码的范畴。cloudflare-os 的完整形态,应该是“源站模板 + 边缘规则定义 + 自动更新检查”三位一体,而不是孤零零一份 nginx 配置。
最后我再分享一个我自己总结的小技巧。Cloudflare 的 IP 列表虽然不是经常变,但一旦变了,源站防火墙和 nginx 的真实 IP 信任列表就可能出现过期。我的建议是把 IP 列表同步写成脚本,定期从官方接口拉取最新网段,自动生成 nginx 的 include 文件和防火墙规则,然后 reload。踩过几次“突然全部访问日志变成边缘 IP”的坑之后,我再也不愿意手动去维护那串列表了。
cloudflare-os 这个名字,说到底是为了提醒自己:边缘不是外挂,边缘本来就是我们系统的一部分。把那层写在 Cloudflare 控制台里的逻辑,和写在服务器里的逻辑放在同一个轨道上思考,复杂流量来了才不会慌。希望这套经验和踩坑记录,能让你少走一段我走过的弯路。