Cloudflare Tunnel 自建服务暴露实战:不开端口把内网应用安全推向公网
很多运维和自建玩家都卡在同一个问题上:家里的 NAS、内网的 Web 面板、或者一台跑着小工具的服务,想从外面访问,但运营商给的是 CGNAT、没有公网 IP,或者干脆不敢在路由器上开 443 端口。传统做法是端口映射加反向代理 VPS,要么有安全风险,要么有成本。Cloudflare Tunnel(前身 Argo Tunnel)给出第三条路:不开任何入站端口,把服务经一条加密隧道推到 Cloudflare 边缘,再转给访客。本文只讲运维落地,不讲营销话术。
1. 它到底解决了什么:连接方向反过来了
端口映射的模型是“公网主动连进来”:你告诉路由器“443 端口的包转给内网那台机器”,于是这台机器必须有一个监听端口永远对全网开放。Tunnel 反过来:你机器上的 cloudflared 守护进程主动向外连到 Cloudflare,维持一条只出不进的长连接。访客访问你的域名时,流量先到 Cloudflare,再沿这条已有的连接回到你机器。你的内网从不需要接受任何入站连接,因此没有端口可供扫描器发现,家庭公网 IP 也永不暴露。这是安全模型的根本改变,而不只是“少配一条映射”。
2. 安装与认证
cloudflared 是 Cloudflare 出的单二进制,Linux/macOS/Windows 都支持。Debian/Ubuntu 走官方仓库:
curl -fsSL https://pkg.cloudflare.com/cloudflare-main.gpg | sudo tee /usr/share/keyrings/cloudflare-main.gpg >/dev/null
echo 'deb [signed-by=/usr/share/keyrings/cloudflare-main.gpg] https://pkg.cloudflare.com/cloudflared any main' | sudo tee /etc/apt/sources.list.d/cloudflared.list
sudo apt update && sudo apt install cloudflared
先登录并绑定你的域名:
这会打开浏览器让你授权域名。授权后创建一个具名隧道,会生成凭证文件并记录隧道 UUID:3. 用 config.yml 编排 ingress
Tunnel 本身不知道要服务什么,规则写在 ~/.cloudflared/config.yml(作为服务运行时实际读 /etc/cloudflared/config.yml):
tunnel: <你的隧道UUID>
credentials-file: /home/youruser/.cloudflared/<你的隧道UUID>.json
ingress:
- hostname: app.yourdomain.com
service: http://localhost:8080
- hostname: ssh.yourdomain.com
service: ssh://localhost:22
- service: http_status:404
http_status:404 是 catch-all,没有它 cloudflared 会拒绝启动。每个 hostname 对应一个内网服务地址和端口,支持 HTTP/HTTPS 与 SSH/VNC 等协议。对 SSH 这类非 HTTP 服务,Cloudflare 会用浏览器里的 web terminal(基于 WASM)渲染,无需本地 SSH 客户端也能用。
4. DNS 自动路由与常驻
把主机名指向隧道(自动建 CNAME),不用手动填 IP:
每个 ingress 里的 hostname 都要对应跑一次。前端验证没问题后,注册成系统服务常驻: 以服务方式运行,机器重启后自动拉起,不用开个终端挂着。5. 给 SSH 上锁:Cloudflare Access
裸把 SSH 通过 Tunnel 暴露出去很危险,必须叠加身份校验。在 Cloudflare Zero Trust 控制台里把 ssh.yourdomain.com 注册为自托管应用,建一条策略:只允许你自己的邮箱,并要求 OTP 或 SSO。这样任何访客在流量触及你服务器之前,必须先过 Cloudflare 的身份关。配合短会话有效期、限定来源地区,能把“公网 SSH 暴破”这个老问题彻底关掉。记住一句话:Tunnel 只是通道,Access 才是锁。
6. 常见故障与排查
- 502 错误:本地服务没起来,或 config.yml 里的端口写错。先
curl http://localhost:8080确认本机可达。 - WebSocket 失败:后端用了自签名证书时,加
originRequest: { noTLSVerify: true }。 - DNS 不解析:CNAME 传播可能要等几分钟,
cloudflared tunnel route dns后耐心等。 - 配置不生效:以服务方式运行时读的是
/etc/cloudflared/config.yml而不是家目录,改完要 cp 过去再重启服务。
7. 免费版的清醒认知
免费版覆盖隧道连接本身,但边界要清楚:Cloudflare TOS 禁止用免费隧道做流媒体分发(Jellyfin/Plex 串流会违规);Argo Smart Routing 是单独付费项,免费回程延迟更高;UDP 支持有限,游戏服务器这类强 UDP 场景不合适。而且 Tunnel 引入了新依赖——你机器在线、cloudflared 运行、Cloudflare 网络正常,三者少一个服务就不可达。对纯静态站点,其实没必要上 Tunnel,直接丢 CDN 更省心;Tunnel 真正划算的场景是“必须跑在你自己硬件上的有状态服务”。
8. 上线后的巡检与防退化纪律
Tunnel 是“会悄悄退化”的典型:cloudflared 进程被 OOM 杀掉、凭证文件权限错了、CNAME 被误删,服务就无声消失。建立纪律:
- 每日:外部拨测你的主机名是否返回 200,监控 cloudflared 进程存活与到 Cloudflare 的延迟。
- 每周:确认凭证文件权限和到期( Tunnel 凭证长期有效,但绑定的域名 NS 若迁走会失效)。
- 多实例:同内网至少跑两个 cloudflared 实例,Cloudflare 按就近和健康度 failover,避免单机单点。
- 每次变更前:先在 staging 用
cloudflared tunnel run前台跑通,再切服务;改完 ingress 立刻 dig 主机名确认 CNAME 生效。 - 红线:禁止裸暴露 SSH 不加 Cloudflared Access;禁止为图省事把整个内网段通过 Tunnel 无差别暴露。