Cloudflare Tunnel 重建实录:一次误删引发的 3 小时抢修

起因:一行命令干掉所有服务

某天为了优化博客访问,给裸域 加了 Redirect Rule 跳转到 。结果发现订阅链接 被短路到博客首页,订阅失效。

排查时手一抖,把整个 Cloudflare Tunnel 删了

这一删,连锁反应:

服务状态
SSH 通道( ❌ 断
Clash 订阅( ❌ 断
代理(Xray)❌ 断

VPS 的 SSH 端口被机房封了,只能靠 Tunnel 进去。Tunnel 没了,VPS 变成孤岛,只能用提供商网页终端操作。

架构回顾

先理清楚原本的架构:

客户端 Clash (VLESS+WS+TLS)
  → Cloudflare 边缘 (
  

:443)
  → Cloudflare Tunnel (QUIC 隧道, VPS 主动外连)
  → cloudflared (VPS)
  → Xray (127.0.0.1:443)
  → 互联网

VPS 上的 cloudflared 用 config.yml + credentials 模式,三条 ingress 规则:

ingress:
  - hostname: 
  


    service: ssh://127.0.0.1:
  


  - hostname: 
  


    path: 
  


    service: http://127.0.0.1:
  


  - hostname: 
  


    service: http://127.0.0.1:443
  - service: http_status:404

三个本地服务:

  • 127.0.0.1: — SSH(VPS 实际 SSH 端口,非标准 22)
  • 127.0.0.1: — Python 订阅服务
  • 127.0.0.1:443 — Xray 代理

重建 Tunnel(任务1)

通过 API 创建新 Tunnel

旧 Tunnel ID 彻底没了(连已删除记录都查不到),只能新建。

通过 Cloudflare API 创建新 Tunnel:

curl -X POST "https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/cfd_tunnel" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name":"xray","tunnel_secret":"..."}'

返回新 Tunnel ID:

VPS 重连 cloudflared

写了一键脚本,在网页终端执行:

# 1. 写入新凭证
cat > /root/.cloudflared/
  

.json << 'EOF'
{
  "AccountTag": "...",
  "TunnelID": "
  

",
  "TunnelSecret": "..."
}
EOF

# 2. 更新 config.yml(换 tunnel ID + credentials 路径)
# 3. 重启 cloudflared
systemctl restart cloudflared

成功标志:日志出现 Registered tunnel connection,precheck 全部 PASS。

改 DNS

SSH 子域的 CNAME 还指向已删除的旧 Tunnel,需要改成新 Tunnel:

记录旧值新值
CNAME ...cfargotunnel.com ...cfargotunnel.com

改完后 SSH 恢复,终于不用再用网页终端了。

裸域冲突(任务2,核心矛盾)

问题

裸域 现在有冲突:

  • 博客:裸域 DNS 指向 Cloudflare Pages + Redirect Rule 跳 www
  • VPS 服务:Tunnel ingress 想让裸域 和 443 流量进 VPS

两者冲突,Redirect Rule 会短路 Tunnel 的裸域规则。

三个方案

方案做法优缺点
A撤销裸域跳转,裸域归 VPS订阅/代理恢复,但博客裸域 404
B订阅/代理改用独立子域各服务独立,但需改配置 + 加 DNS
CRedirect Rule 加路径例外两全其美,但维护负担重

最终选择:方案 B

考虑了方案 C 的弊端后,选择方案 B——每个服务用独立子域

子域用途
博客(Pages)
博客裸域跳 www
SSH 通道
订阅服务
代理

优点:

  • 路径不暴露在公网
  • 各服务独立,互不干扰
  • 不依赖 Redirect Rule 例外
  • 以后加服务直接加子域

实施

更新 VPS config.yml:

ingress:
  - hostname: 
  


    service: ssh://127.0.0.1:
  


  - hostname: 
  


    service: http://127.0.0.1:
  


  - hostname: 
  


    service: http://127.0.0.1:443
  - service: http_status:404

Cloudflare DNS 加两条 CNAME:

  • sub ...cfargotunnel.com(Proxied)
  • xray ...cfargotunnel.com(Proxied)

踩过的坑

坑1:/sub 后端是 8080 不是 443

之前没看完整 config,误以为 后端是 443(Xray),通过 API 改成了 http://127.0.0.1:443。实际 的后端是 (Python 订阅服务)。

教训:改配置前一定要看完整文件,不要凭记忆。

坑2:Tunnel 删除不可恢复

Cloudflare Tunnel 删除是彻底删除,连已删除记录都查不到,无法恢复。凭证文件失效,DNS 记录变成悬空指向。

教训:删 Tunnel 前先备份 ID 和凭证,或先停用不删除。

坑3:server/servername/Host 三个字段要同步改

改用 子域后,订阅文件里三个字段都要改:

字段含义原值新值
server连接地址
servernameTLS SNI
Host(ws-opts headers)WebSocket Host 头

只改 server 不改 servernameHost,TLS 握手能成功但 WebSocket 升级会失败,表现为 timeout。

坑4:HEAD 方法返回 501

Python BaseHTTPServer 不支持 HEAD 方法,curl -I 返回 501。但 Clash Verge 用 GET 拉取订阅,不受影响。

教训:测试时用 curl -s 而不是 curl -I,避免被 HEAD 限制误导。

坑5:Clash 缓存旧配置

改完订阅文件后,Clash Verge 不一定会立即拉取新配置。需要删除旧订阅重新添加,而不是只点刷新。

坑6:wrangler OAuth token 过期

wrangler 的 OAuth token 有效期约 8 小时,过期后 API 调用返回 403。而且该 token 没有 dns_records 写权限,DNS 改动必须手动在控制台操作。

教训:长期自动化需要用 API Token(手动生成,权限可控),不要依赖 OAuth token。

最终验证

# SSH 恢复
ssh -p 2222 root@127.0.0.1  # ✅

# 订阅服务
curl -s https://
  


  

 | head -1
# subscription-userinfo: upload=...; download=...; total=2199023255552  ✅

# 代理链路
curl -I https://
  

/Xray
# HTTP/2 400  ✅(普通HTTP访问WS端点必然400)

# 博客
curl -I https://
  

/
# HTTP/2 200  ✅

教训总结

  1. Tunnel 删除不可逆,操作前备份 ID 和凭证
  2. 改配置前看完整文件,不要凭记忆
  3. 子域方案优于路径例外,维护更简单
  4. server/servername/Host 要同步,少一个都连不上
  5. OAuth token 会过期,长期用 API Token
  6. 测试用 GET 不用 HEAD,避免方法限制误导

整个恢复过程花了约 3 小时,大部分时间花在网页终端操作(不能粘贴长命令)和理解 Cloudflare 规则优先级上。以后类似的故障,有了这次的脚本和经验,应该 30 分钟内能恢复。

CloudflareTunnelVPS故障恢复教训