利用 Cloudflare Tunnel 与 WebSocket 入站实现内网服务的零公网 IP 暴露
【摘要】 整套方案零公网 IP、零端口暴露,所有流量都走 TLS 加密的 Cloudflare 网络,适合自托管面板、家庭 NAS 服务等场景。最大的心得是:**"进程在跑"不等于"服务可用",端口监听验证和分层 curl 探测是排障的两个基本功**。希望这篇分享能帮你少踩几个坑。
## 前言
在日常开发和自托管场景中,我们经常遇到这样的问题:手头有一台没有公网 IP 的机器(家宽 NAS、校园网服务器、云上容器等),想把它上面的服务安全地暴露出去,又不想在路由器上开端口转发。本文分享我实践过的一套方案:**Cloudflare Tunnel(Argo)+ WebSocket 类入站代理**,实现零公网 IP、零端口开放的服务暴露,并记录了排查"隧道通但业务不通"问题的完整思路。
## 一、整体架构
```
用户客户端
│ TLS
▼
Cloudflare Edge(域名解析,代理开启)
│ WSS 加密隧道(cloudflared 主动外连,无需入站端口)
▼
本机 cloudflared 进程
│ HTTP 回源 localhost:PORT
▼
服务端应用(支持 WebSocket 路由分发)
```
核心思想:**让云端主动"出站"连接 Cloudflare**。cloudflared 与边缘节点建立长连接后,所有访问指定域名的流量都会通过这条隧道回源到本机,防火墙只需要允许出站 7844 端口即可。
## 二、实施步骤
### 1. 创建隧道
在 Cloudflare Zero Trust 控制台创建 Tunnel,得到 token(或 TunnelSecret JSON),在服务器上运行:
```bash
cloudflared tunnel --no-autoupdate --protocol http2 run --token <TOKEN>
```
然后把域名(如 `app.example.com`)绑定到该隧道,回源地址填 `http://localhost:8001`。
### 2. 服务端多协议复用一个端口
为了在一台机器、一个隧道端口上承载多种业务路由,我用了一个带 fallback 的入站设计:
- 主入站监听 `8001`,按路径分发:
- `/vless-argo` → 内部 3002(WebSocket)
- `/vmess-argo` → 内部 3003(WebSocket)
- `/trojan-argo` → 内部 3004(WebSocket)
- 其他流量 → 直接回落 3001
这样 Cloudflare 回源只打一个端口,内部路由互不干扰,运维上非常清爽。
### 3. 客户端配置要点
以 vmess + ws + tls 为例,客户端关键参数:
| 参数 | 值 |
|------|-----|
| 地址 | 优选域名/IP(如 saas 类优选域名) |
| 端口 | 443 |
| 传输 | WebSocket |
| path | `/vmess-argo?ed=2560`(ed 参数启用 0-RTT) |
| Host/SNI | 隧道域名 |
**注意**:优选域名解析到的是 Cloudflare 边缘节点,不是你的服务器;真正的回源靠 SNI + Host 匹配到隧道。
## 三、踩坑记录:隧道通、节点不通
这是最有价值的一部分。我遇到过控制台显示连接器在线、`curl https://隧道域名/` 也有响应,但实际业务握手失败的情况。排查思路如下:
### 1. 先分层定位
```bash
# ① 隧道层:本地回源是否正常
curl -i -H "Upgrade: websocket" -H "Connection: Upgrade" \
-H "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==" \
-H "Sec-WebSocket-Version: 13" http://127.0.0.1:8001/vmess-argo
# 期望 101
# ② 公网层:走域名测试
curl -i --http1.1 -H "Upgrade: websocket" ... https://app.example.com/vmess-argo
```
- ① 通、②不通 → 问题在 Cloudflare 回源配置(回源端口、ingress 规则)
- ① 就不通 → 问题在本机服务(大概率是入站没起来)
### 2. 本机入站"静默失败"的经典陷阱
服务端二进制用 `nohup app -c config.json >/dev/null 2>&1 &` 启动,**如果 8001 被其他进程占用,xray/核心会启动失败,但日志被吞掉了**,主进程还活着,看起来一切正常。
解决:启动后必须验证端口真的在监听:
```bash
ss -tlnp | grep -E ':(8001|3001|3002|3003|3004)'
```
我在脚本里加了预检逻辑:启动前用 Node.js 逐端口尝试绑定,被占用直接报错提示换端口;启动后轮询探测端口进入 LISTEN 状态才继续。自此再没被这个问题坑过。
### 3. 530 vs 502 的含义
- **530**:隧道本身没连上(cloudflared 进程死了或 token 失效)→ 查 cloudflared
- **502**:隧道通,但回源失败(服务没监听 / 端口不对)→ 查本机服务和回源配置
记住这两个码,排查速度能快一倍。
## 四、稳定性建议
1. **固定隧道 + 固定回源端口**:临时隧道(trycloudflare.com)域名每次重启都变,订阅类场景务必用固定隧道。
2. **子进程看护**:cloudflared 和业务核心用 systemd 或 pm2 托管,并配置崩溃自动重启。
3. **高位端口防冲突**:回源端口选 50000+ 的高位端口,避开容器环境常见的 8000/8080。
4. **WebSocket 路径加随机化**:路径里带一段随机串,减少被扫描器探测的概率。
## 结语
整套方案零公网 IP、零端口暴露,所有流量都走 TLS 加密的 Cloudflare 网络,适合自托管面板、家庭 NAS 服务等场景。最大的心得是:**"进程在跑"不等于"服务可用",端口监听验证和分层 curl 探测是排障的两个基本功**。希望这篇分享能帮你少踩几个坑。
---
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
评论(0)