起因:一行命令干掉所有服务
某天为了优化博客访问,给裸域 加了 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 |
| C | Redirect 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 | 连接地址 | ||
servername | TLS SNI | ||
Host(ws-opts headers) | WebSocket Host 头 |
只改 server 不改 servername 和 Host,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 ✅
教训总结
- Tunnel 删除不可逆,操作前备份 ID 和凭证
- 改配置前看完整文件,不要凭记忆
- 子域方案优于路径例外,维护更简单
- server/servername/Host 要同步,少一个都连不上
- OAuth token 会过期,长期用 API Token
- 测试用 GET 不用 HEAD,避免方法限制误导
整个恢复过程花了约 3 小时,大部分时间花在网页终端操作(不能粘贴长命令)和理解 Cloudflare 规则优先级上。以后类似的故障,有了这次的脚本和经验,应该 30 分钟内能恢复。