ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

用Cloudflare边缘网络架构cloudflare-os:统一家庭服务器入口与认证

用Cloudflare边缘网络架构cloudflare-os:统一家庭服务器入口与认证

如果你看到“cloudflare-os”这个名字,很容易觉得它是个能直接装进 U 盘的操作系统镜像。其实它是我给自己那套“把零散自建服务统一收拢到 Cloudflare 边缘”的方案起的代号,不是官方发行版,也不需要单独安装。起因特别简单:我在角落里放了一台飞牛OS(fnOS)小主机,上面跑着文件服务、照片备份、EasyNVR 监控录像,另外还有两台 Linux 服务器跑着一堆 Docker 容器。东西一多,问题就来了:每个服务各占一个端口,今天记 5666,明天记 8080,后天记 7443,一个月不看自己都想不起来;出门在外想看监控,先翻笔记找域名;更麻烦的是面板账号全是独立的,密码多了记不住,密码少了不放心。

cloudflare-os 就是冲着这三个痛点去的:统一入口、统一认证、统一数据层。所有服务对外只暴露一个域名空间,用户在浏览器里输入地址,过完身份校验后,请求就会被自动路由到对应的本地服务。整个过程不需要给每台机器单独配证书,不需要在路由器上开一堆端口,也不需要租一台带着公网 IP 的 VPS 来做转发。这套体系跑下来以后,我自己对家里这些“零散服务器”的管理负担明显下降了一个量级。

如果你手里有 NAS,或者跑着若干个 Docker 服务,又不想为了把服务安全暴露到公网去折腾公网 IP、VPS 和安全网关,那这篇文章应该对你有参考价值。下面我会从设计思路、架构拆解、实操配置到故障排查,完整讲一遍 cloudflare-os 的落地过程。

1. 整体设计与思路拆解

1.1 为什么把方案叫作“os”

名字是我自己起的,但确实不是乱起。

操作系统最核心的职责是识别外部请求、调度底层资源、向上层应用提供统一接口。对照一下 cloudflare-os:域名 DNS 放在 Cloudflare,身份验证交给 Cloudflare Access,API 网关由 Workers 承担,备份数据放进 R2,机器上的服务状态记录在 D1,而 cloudflared 负责把本地的 HTTP 服务安全地接入到 Cloudflare 边缘。用户看到的只是某个子域名,但这个请求最后落到哪台机器、由哪个容器处理、需不需要校验身份,全由这套“调度层”说了算。这不就是一个简化版的操作系统吗?所以我管它叫 cloudflare-os。

有朋友会问:那直接用一台 VPS 装个 Nginx 反代,把流量按路径转发到不同机器,不也能达到类似效果?能做,但代价不一样。自建网关意味着你得持续维护一台有公网 IP 的服务器,证书到期要续,组件漏洞要补,还得处理各种扫描和爆破日志。cloudflare-os 的思路是把所有“入口侧”的能力外包给 Cloudflare,本地节点只负责出站连接和执行任务,不暴露任何入站端口。运维负担小很多,安全性也反而更好。

1.2 这套设计解决了哪些具体问题

我把自己遇到的痛点列了一下,很多朋友应该也有同感:

  • 端口记忆成本高。5666、8080、18080、3443 各代表一个服务,时间久了根本分不清谁是谁。
  • 认证分散。每个面板独立账号,改密码要改一圈,走的时候又容易漏掉某个服务。
  • 直接暴露公网怕被扫描。只要把端口映射到公网,机器几乎立刻就会遇到来自全球的扫描探测。
  • 备份没有统一出口。NAS 一份备份、Docker 服务器一份备份,互相之间没有关联,出问题想恢复,得先搞清楚数据在哪儿。
  • 没有中心元数据。服务状态、设备清单、备份索引完全靠手工记录,查一次翻一次聊天记录。

cloudflare-os 这层调度解决了以上问题后,最终的体验是:我不管在哪个子域名后面跑着什么,只需要记住一个 Access 登录入口,进去之后所有服务都按我的权限展示。

1.3 选型时比较过的几条路线

做这个项目之前,我认真对比过几个方案,不是一拍脑袋就上的。

方案需要公网IP证书维护统一认证安全防护数据备份运维成本
自建 Nginx 网关需要自己续期自己对接依赖防火墙规则自己搭服务较高
云服务器 + frp 类工具需要自己处理无一般自己搭服务较高
cloudflare-os不需要Cloudflare 自动Access 原生边缘层防护R2 + D1 原生很低

真实情况是,我的宽带根本没有固定公网 IP,所以自建网关这条路天然走不通。与其花精力找中间方案,不如直接用 Cloudflare 这套成熟的产品组合。它给个人项目留了很可观的免费额度,这一点后面会细说。

2. 核心架构与关键原理

2.1 一次请求从浏览器到本地服务的完整链路

先画一遍数据流,这样后面配置的时候你心里会有数。

用户在浏览器输入 https://nas.example.com ,首先 DNS 解析会指向 Cloudflare 的 Anycast 地址。此时流量已经进入 Cloudflare 边缘网络,如果这个域名在 Access 里被保护,Cloudflare 会先检查用户有没有登录态,没有就重定向到登录页,让用户通过邮箱验证码完成身份校验。校验通过后生成一个短暂的 cookie,后续请求都会携带这个凭证。

接着,Cloudflare 边缘会根据 hostname 找到对应的路由规则,把请求通过一条长期保持的出站连接转给本地运行的 cloudflared 进程。cloudflared 再把请求转交给本机的服务,比如 127.0.0.1:5666。响应原路返回。这里最关键的一点是,cloudflared 是从本机主动向外连接 Cloudflare 边缘的,所以整个链路不需要在路由器上开任何入站端口,也不会把源站 IP 暴露给公网。

2.2 各个组件在项目里的角色

组件作用为什么用它
Cloudflare DNS域名解析和路由接管域名解析后,路由记录可以用 Tunnel 类型自动绑定
Cloudflare Access身份准入不依赖应用自身的账号体系,统一登录入口
cloudflared本地与边缘的安全通道出站连接,避免暴露端口和源站 IP
WorkersAPI 网关、业务逻辑全球边缘执行,延迟低,支持 D1/R2 绑定
R2对象存储免出口流量费用,适合做异地备份
D1元数据数据库存设备清单、备份索引、访问日志

Access 是 cloudflare-os 最重要的“门卫”。你可以理解为,所有服务都藏在 Access 后面,只有通过了身份校验的人,才会被引导到真实应用。这样即便某个服务本身弱密码、有漏洞,外部攻击者也没有机会碰到它,因为连应用本身都摸不到。

2.3 免费额度盘点:个人场景够不够用

做这个方案之前,我怕 Cloudflare 收费,专门把免费套餐的额度逐项看了一遍。

  • DNS 托管免费,无限流量,对个人域名完全足够。
  • Access 免费版支持 50 个以内用户,家庭场景三五个人随便用。
  • Workers 每天有 10 万次请求额度,跑几个 API 转发和定时任务完全够。
  • R2 本身免出口流量费用,存储费用几乎可以忽略,适合备份场景。
  • D1 每天有 5 万行读、1000 行写的免费额度,用来存设备清单、文件索引够用。

累计下来,只要不是拿它当生产级大流量站点,free plan 真的可以撑起一个很完整的个人云体系。这也是我最终选择全套 Cloudflare 而不是自己搭一套的原因。

3. 实操过程与核心环节实现

3.1 第一步:把域名切到 Cloudflare

cloudflare-os 的前提是有一个自己的域名。无论域名是在哪家注册商买的,都可以把域名托管给 Cloudflare。操作路径是在 Cloudflare 控制台选择 Add a site,输入域名,它会提示你修改域名的 NS 记录。去自己的注册商后台把 NS 改成 Cloudflare 给的两个地址,等生效即可。

这里有一个小经验:不要在域名还没切换完成的时候就开始配 Tunnel,因为 DNS 记录解析不生效,后面验证会把你绕晕。等 Cloudflare 提示 Active,也就是 NS 完全生效之后,再继续下一步。

3.2 配置 Cloudflare Access,把域名保护起来

进入 Zero Trust 控制台,找到 Access -> Applications,创建一个 Self-hosted 应用。应用域名填你要保护的子域,比如 nas.example.com。策略是核心,我推荐先用最简单的规则:条件选择 Emails,填你自己的邮箱;再叠加一个 Optional 的邮箱一次性验证码选项。这样用户登录时会收到一个邮件验证码,输进去才能通过。

Session 时长我一般设成 7 天。太短了自己用着烦,太长又怕丢了手机后别人能一直用。7 天是折中值。另外建议在 Access 策略里把“允许的 IP 段”加上自家宽带的出口 IP,这样日常使用完全不需要验证码,只有出门在外才走邮箱验证码。不过要注意,如果宽带出口 IP 是动态的,就得定期去改,或者干脆不用 IP 限制,只靠验证码。

3.3 本地安装 cloudflared,把服务连到边缘

cloudflared 是整套体系的最后一米。安装方式很简单,Linux 上可以直接下载二进制包,或者使用包管理器。飞牛OS 基于 Debian 体系,所以用 deb 包或者二进制文件都行。我第一次踩坑是在 Windows 上装的,那个另说,后面会专门讲。

登录并创建 Tunnel 的命令:

cloudflared tunnel login cloudflared tunnel create nas

执行完 create 后,cloudflared 会在本地生成一个 Tunnel 的凭证文件。注意保存好这个文件,它相当于这台机器的“通行证”。接着写配置文件 /etc/cloudflared/config.yml:

tunnel: <tunnel-id> credentials-file: /var/lib/cloudflared/<tunnel-id>.json ingress: - hostname: nas.example.com service: http://127.0.0.1:5666 - hostname: docker-ui.example.com service: http://127.0.0.1:8080 - hostname: monitor.example.com service: http://127.0.0.1:18080 - service: http_status:404

这个配置文件的逻辑是:哪个子域名对应哪个本地端口。最后一条 default 规则返回 404,是为了避免没匹配到的请求落到空处。

写完后先手动跑一次:

cloudflared tunnel run nas

如果日志里显示Registered tunnel connection,说明已经通了。然后绑定 DNS 路由:

cloudflared tunnel route dns nas nas.example.com

这条命令会自动在 Cloudflare DNS 里添加一条 Tunnel 类型的 CNAME 记录,不需要你去手填 IP。

3.4 让 cloudflared 后台常驻

确认手动运行没问题后,再把 cloudflared 注册成系统服务。Linux 上执行:

sudo cloudflared service install sudo systemctl enable cloudflared --now

此时要检查一下运行用户和目录权限。我刚开始直接用 root 跑,遇到了一堆权限问题,后面排查常见问题时细说。正确做法是给 cloudflared 建一个专用用户,把配置文件目录和凭证文件的所有者改成这个用户,然后在 systemd unit 里指定 User 和 Group,避免使用 root 来运行网络组件。

3.5 用 Workers 做统一的 API 网关

单纯靠 Tunnel 做路由,只能解决“页面能访问”的问题。我还有一个需求是让本地的 NAS、Docker 面板能通过同一种 API 风格被外部调用,并且记录调用元数据。这个任务我交给了 Workers。

在 Cloudflare 控制台创建一个 Worker,绑定一个子域 api.example.com,然后写入类似下面的逻辑:

export default { async fetch(request, env, ctx) { const url = new URL(request.url); const path = url.pathname; if (path.startsWith('/nas/')) { const result = await env.NAS_DB.prepare('SELECT * FROM devices WHERE type = ?').bind('nas').all(); return corsResponse(JSON.stringify(result), request); } if (path.startsWith('/backup/')) { const key = url.searchParams.get('key'); const object = await env.BACKUP_BUCKET.get(key); return corsResponse(object?.body || 'not found', request); } return corsResponse('ok', request); } }

在实际使用中,Workers 还承担了一个很关键的职责:补全 CORS 响应头。很多 NAS 的 API 原本只允许同源请求,浏览器直接访问会报跨域错误,在 Worker 里加响应头就能解决。

3.6 用 R2 做备份存储,D1 做元数据

R2 是 Cloudflare 的对象存储,最大的特点是免流量出口费用。我把家里的关键数据用 rclone 定期推送到 R2 的 bucket。rclone 配置 R2 时,endpoint 填:

https://<accountid>.r2.cloudflarestorage.com

Access Key 和 Secret Key 在 Cloudflare 的 R2 管理页创建。备份目录就按日期分桶,比如 backups/2025/06/ 下面放当天打包好的压缩文件。这样即使本地硬盘整个挂了,云端还有一份。

D1 则用来存元数据,比如设备列表、任务清单、备份清单。飞牛OS 上的每个容器、每台服务器的地址信息,我都用 Workers 接口写入 D1。想看的时候,浏览器打开 api.example.com/devices,就能拿到一份结构化的清单,不用再翻 Excel 表格。

3.7 把飞牛OS、Docker 服务、FTP 都接进来

飞牛OS 的 Web 管理端默认监听 5666 端口,所以在 cloudflared 配置里把 nas.example.com 指向 127.0.0.1:5666 就行。其他 Docker 服务也一样,只要容器把端口映射到宿主机,就能用同一套方式接入。唯一的建议是,每个服务尽量用子域名区分,不要全挂在根域上,这样 Access 策略会好写很多。

飞牛OS 自带的 FTP 模块起初我也想放到公网用,结果实际测试下来发现很不靠谱。FTP 的被动模式需要客户端额外发起数据连接,这跟 HTTP 的子域名路由模型天生不兼容。所以最终方案是:FTP 只保留在局域网内使用,外网访问改用 SFTP 或 WebDAV。如果你非要用 FTP,建议只对固定 IP 开放,并且不要把 FTP 端口映射到公网,很容易被扫描爆破。

4. 常见问题与排查技巧实录

4.1 报错 “error: 拒绝访问。 (os error 5) to work without the background server, rerun”

这是我在一台 Windows 机器上部署 cloudflared 时最容易遇到的报错。完整提示类似 error: 拒绝访问。 (os error 5) to work without the background server, rerun。第一次看到时我以为是配置问题,后来排查了半天才发现,这本质上就是操作权限不够。

os error 5 在 Windows 上通常是因为运行 cloudflared 的当前用户没有权限创建后台服务相关的文件或目录。解决方法是:以管理员身份打开命令提示符,然后执行 cloudflared service install,再执行 cloudflared service uninstall 重新安装。如果是在 Linux 上报同样的错误,重点看 systemd service 文件里指定的用户有没有访问 /var/lib/cloudflared 目录的权限,以及凭证文件是否属于该用户。

还有一点特别容易忽略:在你已经用 cloudflared service install 把服务注册好之后,不要再习惯性地执行 cloudflared tunnel run 去手动启动。这时候 cloudflared 会尝试去拉一个后台服务,然后就会触发 os error 5。正确操作是直接用 systemctl start cloudflared 启动系统服务。

4.2 Docker overlay2 目录占用越来越大

跑 Docker 一段时间后,/var/lib/docker/overlay2 目录越来越大,这是几乎所有容器玩家都会遇到的问题。overlay2 是 Docker 默认的存储驱动,它记录了镜像层和容器可写层的数据。很多人一看到它占用大,就想直接删目录,这是千万要避免的,直接删会让容器文件系统损坏。

正确的排查路径是:

步骤命令/操作说明
查看占用docker system df看 image、container、local volumes 各占多少
清理悬空docker system prune -a删除所有未被容器使用的镜像和缓存
清理日志修改 daemon.json 限制日志大小日志文件常被忽略,实际上量很大
定期清理脚本定时执行 docker container prune防止残留容器堆积

日志限流尤其推荐。在 /etc/docker/daemon.json 里加入:

{ "log-driver": "json-file", "log-opts": { "max-size": "50m", "max-file": "3" } }

然后重启 Docker。如果不限制日志,一个容器几个月能写出几十 GB 的 json 日志,overlay2 反而只是表象。

4.3 Workers 转发 API 后浏览器报跨域错误

用 Worker 做 API 网关时,最简单的一类问题就是跨域。浏览器看到 api.example.com 返回的响应头里没有 Access-Control-Allow-Origin,就会阻止 JS 读取结果。解决办法是在 Worker 里统一给所有响应加 CORS 头。

function corsResponse(body, request) { return new Response(body, { status: 200, headers: { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET,POST,PUT,DELETE,OPTIONS', 'Access-Control-Allow-Headers': 'Content-Type, Authorization' } }); }

实际生产场景别把 Origin 直接写成 *,最好限制成你自己前端站点的域名。不然任何第三方站点都能跨域读取你的 API 数据,这就有点危险了。

4.4 FTP 服务放到公网后各种连不上

这个问题我在 3.7 里提过,这里再详细说一次。FTP 走的是 TCP 连接模型,控制连接和数据连接可能不是同一个来源端口,cloudflared 这类按域名路由 HTTP 请求的工具没法完整代理 FTP。所以你看到的现象就是:NAS 的 FTP 在局域网里很稳定,一旦把域名放到公网,客户端要么卡在连接阶段,要么能登录但列不出目录。

我的最终方案是彻底放弃对外暴露 FTP,改用 SFTP。SFTP 基于 SSH 通道,天然就是一条连接,适合放到网关后面。配合 Access 的 IP 限制,平时只有固定的 CIDR 段能访问,风险小很多。

5. 一点心得和后续扩展方向

5.1 安全策略一定要“默认拒绝”

cloudflare-os 很大程度依赖 Access 的安全边界,所以策略配置反而是最重要的环节。我见过有人图省事,把 Access 策略的 Include 条件设成“Everyone”,等于没设防。我的做法是:默认拒绝所有访问,只放行自己的邮箱和常用 IP 段;需要临时让朋友访问时,再开一个有时效的邮箱验证码规则,用完就删。Session 时长不要设置成永久,哪怕自己用,7 天够了。

另外,如果你在容器里跑的任何服务不需要被公网访问,就不要给它配 hostname。cloudflare-os 的优势是可以精细控制哪个服务暴露,一定不要把容器全部都塞进 ingress 规则里,那样等于又回到了“端口全开”的状态。

5.2 这套体系还能怎么扩展

我在基本结构跑通之后,又陆续加了几个很实用的扩展。

  • 用 Workers 定时任务做健康检查:每 5 分钟请求一次 NAS 的本地健康检查接口,失败就推送到企业微信机器人。
  • 用 R2 做异地容灾:不止飞牛OS,所有服务器上的重要备份都统一推送到 R2,多套数据形成冗余。
  • 用 D1 记录访问日志:每个请求在 Worker 里写入一条纪录,结合 Access 的日志,能比较完整地知道谁在什么时间访问了哪个服务。
  • 多个物理位置的设备统一接入:在另一台服务器上装一个 cloudflared,共用同一个 Tunnel,按子域名把流量分发到不同机房,这种感觉真的很像一个“分布式操作系统”。

最后分享一个我自己一直在用的小技巧:每个月月初,我用 Worker 里的定时任务跑一遍 R2 对象数量和 D1 记录数的对账,看看备份文件有没有缺失,再调一下 R2 生命周期规则,把 30 天前的旧备份自动删除。这套 cloudflare-os 跑了大半年,最舒服的一点是终于不用再记端口了,所有服务都像操作系统里的应用一样,挂在一个入口下面,清清楚楚。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进