把个人站和个人服务一起挂上 Cloudflare:Workers 与 Tunnel 的分工

静态的交给 Workers,长连接的交给 Tunnel,一个域名把两件事都办了。

2026-09-15·7 分钟阅读·任增浩

这个站要同时干两件事:一个是随时能打开的个人主页和简历,另一个是把跑在云服务器上的多人游戏暴露到公网。这两件事性质完全不同——前者是纯静态文件,后者是一个需要长期在线的 Node 进程,还带着 WebSocket。所以我没有用一套方案硬扛,而是拆成两条路走。

静态的部分:Workers 静态资源

主页、简历、项目页加起来只有几个 HTML 文件和几张图片,没有数据库,也没有登录。这种情况下最省事的做法是把整个目录交给 Cloudflare Workers 的静态资源托管,配置文件里声明目录和自定义域名,然后一条命令部署:

{
  "name": "wxekre",
  "assets": {
    "directory": "./网页",
    "html_handling": "auto-trailing-slash",
    "not_found_handling": "404-page"
  },
  "routes": [
    { "pattern": "wxekre.cn", "custom_domain": true },
    { "pattern": "www.wxekre.cn", "custom_domain": true }
  ]
}

改完 HTML 跑一次 wrangler deploy 就全量更新。没有服务器要续费、要打补丁、要担心到期,也不存在「证书过期了整站打不开」这种事。

auto-trailing-slash 让 /resume 直接对应 resume.html:链接写起来干净,文件本身还是普通静态文件,编辑起来没有任何门槛。

动态的部分:为什么不能直接把端口暴露出去

游戏服务是 Node.js + Socket.io,长连接、要求低延迟、必须一直在线。最直觉的做法是在云服务器上开个端口,把域名的 A 记录指过去。我一开始就是这么干的,然后撞上了下面这个坑。

坑:.cn 域名 + 大陆服务器

把 game 子域解析到服务器公网 IP 之后,浏览器打开返回的不是游戏,而是一张备案不合规的提示页;换成直接在服务器上用 curl http://127.0.0.1:3000 访问,反而是正常的 200。

这说明被拦的不是端口,而是域名——云厂商是在 HTTP 层按请求里的 Host 头做检查的。只要请求还带着这个域名进到那台服务器,就过不去。

Cloudflare Tunnel:让请求不走入站端口

Tunnel 的思路正好绕开这一点:服务器上的 cloudflared 主动向 Cloudflare 边缘建立几条出站长连接,外部请求先到 Cloudflare 边缘,再由隧道送回服务器本机。整个过程服务器的入站端口一个都不用开,那层基于 Host 的检查自然也不会被触发。

配置本身很短,/etc/cloudflared/config.yml 里写清楚域名和本地服务即可:

tunnel: <tunnel-id>
credentials-file: /etc/cloudflared/<tunnel-id>.json
ingress:
  - hostname: game.example.com   # 示例;实际已统一为 wxekre.cn/game/
    service: http://127.0.0.1:3000
  - service: http_status:404

然后把 DNS 里那条指向服务器 IP 的 A 记录换成指向隧道的 CNAME,并且打开代理(橙色云朵):

类型名称内容代理状态
CNAMEgame<tunnel-id>.cfargotunnel.com已代理

服务用 systemd 托管,加上 Restart=on-failure,服务器重启后隧道会自动重连,不需要人工盯着。

最后的分工

用途承载方式地址
个人主页 / 在线简历Workers 静态资源wxekre.cn
多人实时对战游戏Nginx → 本机 3000wxekre.cn/game/

一句话总结:能静态就静态,必须动态就用隧道,别让任何东西直接裸奔在公网端口上。

部署Cloudflare
← 返回首页 看看这个游戏 →