Cloudflare Tunnel 内网穿透实战:无公网 IP 发布服务、ingress 配置与 Zero Trust 加固

没有公网 IP,怎么把家里的服务放到网上

个人站长常遇到这种场景:家里或者办公室有一台性能不错的机器,想把它变成一台服务器——跑个博客、做个私有网盘、放个监控面板、开个测试环境。但问题是:运营商给你的宽带没有公网 IPv4 地址(现在绝大多数家庭宽带都是 NAT 后的私有地址),或者路由器虽然在,但你不想为了一个服务把整台机器暴露在公网扫描下面。

传统的方案是内网穿透:frp、ngrok、花生壳之类。它们的原理都是"内网机器主动连出去,把外部请求隧道回内网"。frp 很好用,但需要你自己有一台有公网 IP 的服务器做中转,而且要自己维护 frps 服务端、自己处理 TLS 证书、自己写防火墙规则。

Cloudflare Tunnel(cloudflared)把这件事的运维成本降了一个数量级:你不需要自己有公网服务器,不需要开任何入站端口,不需要自己申请证书。内网机器上跑一个 cloudflared 进程,它主动向 Cloudflare 边缘建立出站连接,Cloudflare 那边把来自你域名的请求通过这条隧道打回内网。对外界来说,你的服务就是一个正常的 HTTPS 网站。

原理:出站连接取代入站端口

理解这一点很关键,因为它决定了安全模型。传统做法需要你的路由器做端口转发:外网访问 你的公网IP:443 → 转发到内网 192.168.1.10:80。这要求:有公网 IP、路由器开放端口、防火墙放行。任何一条不满足就用不了,而且一旦开放,全世界都能扫到这个端口。

Cloudflare Tunnel 反过来:cloudflared 在启动时主动向 Cloudflare 的边缘节点发起出站的长连接(走 443 或 7844 端口,都是出站方向),然后 Cloudflare 把发给 你的域名 的请求通过这条已建立的连接送回来。因为连接是内网主动发起的,NAT 和防火墙天然允许,所以:

路由器不用做任何端口转发;公网 IP 完全不需要(哪怕是 CGNAT 后面的宽带也行);内网机器不监听任何公网端口,扫描器扫不到;所有请求都经过 Cloudflare,自动获得它的 DDoS 防护、WAF 和全球加速。

代价是流量必须经过 Cloudflare,所以它对你的服务内容是有可见性的(HTTPS 到 Cloudflare 边缘这一段是解密的,Cloudflare 到内网这一段由 cloudflared 自己加密)。对于博客、网盘、测试环境这类个人用途完全没问题;如果是存放高敏感数据,要清楚这一点。另外 Cloudflare 的服务条款对"大量非网站流量"(比如拿它做纯代理转发大文件、跑流媒体中转)是有限制的,不要拿来当翻墙或大流量 CDN 用。

准备:域名托管到 Cloudflare

Cloudflare Tunnel 要求你的域名使用 Cloudflare 的 DNS。如果域名还在别处,先去 Cloudflare 添加站点、把域名的 NS 记录改成 Cloudflare 分配的两个域名服务器,等解析生效(通常几分钟到几小时)。这一步是必须的,因为隧道要绑定一个走 Cloudflare 解析的主机名。

然后确认你的域名已经有一个能正常访问的记录——哪怕是占位用的。之后我们会在 cloudflared 里创建一个新的、指向隧道的 CNAME,替换掉指向你内网 IP 的那条。

如果只是想先测试,Cloudflare 提供的快速隧道(trycloudflare.com)连域名都不需要,跑一条命令就给你一个随机 URL。但那个地址是临时的、每次重启都变、不能自定义域名,只适合临时演示,不适合长期使用。正式方案还是走命名隧道(named tunnel)。

安装 cloudflared

内网那台机器上执行。Debian/Ubuntu 用官方仓库最省事,能自动随系统更新:

# 添加 Cloudflare 官方源
sudo mkdir -p --mode=0755 /usr/share/keyrings
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 -y cloudflared

# 验证
cloudflared --version

如果你的环境是 Docker(比如群晖、或者一台专门跑容器的机器),官方镜像更好管理,因为配置可以整体搬走:

docker run -d --name cloudflared --restart unless-stopped \
  cloudflare/cloudflared:latest tunnel --no-autoupdate run --token 你的隧道token

Docker 方式下要注意:容器里的 localhost 是容器自己,不是宿主机。所以隧道指向的服务地址不能写 http://localhost:8080,得写 http://host.docker.internal:8080(Linux 上要加 --add-host=host.docker.internal:host-gateway),或者干脆让目标服务也跑在同一个自定义网络里、用服务名互访。这是 Docker 跑 cloudflared 最常见的"隧道通了但报 502"原因。

创建隧道并绑定域名

在浏览器里跑一次登录授权(会打开一个页面让你选域名,这一步是为了让 cloudflared 拿到在你这台机器上创建隧道的权限):

cloudflared tunnel login

它会在 ~/.cloudflared/ 下生成一个 cert.pem,这就是你在这台机器上的授权凭证。然后创建隧道:

cloudflared tunnel create blog-home
# 输出:Created tunnel blog-home with id 8a7b3c1d-....

记住这个隧道 ID,它会生成一个同名的凭据文件 ~/.cloudflared/<隧道ID>.json。接下来把域名指向隧道:

cloudflared tunnel route dns blog-home blog.example.com

这条命令会在 Cloudflare DNS 里自动创建一条 CNAME,把 blog.example.com 指向 <隧道ID>.cfargotunnel.com。DNS 默认是橙色云(走代理),不要手动改成灰色——隧道必须走 Cloudflare 边缘才能工作。

然后是配置文件 ~/.cloudflared/config.yml,这决定了"哪个域名打回内网的哪个服务":

tunnel: 8a7b3c1d-你的隧道ID
credentials-file: /root/.cloudflared/8a7b3c1d-你的隧道ID.json

ingress:
  # 主站打到本机 Nginx
  - hostname: blog.example.com
    service: http://localhost:80
  # 私有网盘打到另一个端口
  - hostname: pan.example.com
    service: http://localhost:8080
  # SSH 走隧道(不占公网 22 端口)
  - hostname: ssh.example.com
    service: ssh://localhost:22
  # 兜底规则:所有未匹配的请求返回 404
  - service: http_status:404

ingress 是按顺序匹配的,从上往下第一条命中的生效,所以最后必须有一条不带 hostname 的兜底规则,否则 cloudflared 会拒绝启动(它会校验"规则必须完整覆盖所有情况")。这条兜底写 http_status:404 是标准做法——不匹配的域名直接给 404,不会意外暴露内网服务。

跑成系统服务并验证

# 安装为 systemd 服务
sudo cloudflared --config /root/.cloudflared/config.yml service install

# 如果配置文件在 /root 下,systemd 以 cloudflared 用户跑会读不到
# 更稳的做法是把配置和凭据移到 /etc/cloudflared/ 并改属主
sudo mkdir -p /etc/cloudflared
sudo cp /root/.cloudflared/config.yml /root/.cloudflared/*.json /etc/cloudflared/
sudo chown -R cloudflared:cloudflared /etc/cloudflared
sudo chmod 600 /etc/cloudflared/*.json
# 同时把 config.yml 里的 credentials-file 路径改成 /etc/cloudflared/...

sudo systemctl enable --now cloudflared
sudo systemctl status cloudflared
journalctl -u cloudflared -n 50 --no-pager

日志里看到 Registered tunnel connection 并列出几个 connIndex,就说明隧道建立了。然后从任意有网的地方访问 https://blog.example.com,应该能看到你的站点,浏览器地址栏是正常的小锁。

如果报 502 Bad Gateway,几乎总是这三个原因:目标服务没在跑(curl -I http://localhost:80 在宿主机自测一下);Docker 场景下写了 localhost 而不是宿主机地址;或者目标服务只监听了 127.0.0.1 而 cloudflared 在另一个网络命名空间里(Docker 场景),需要让服务监听 0.0.0.0 或者加入同一个容器网络。

如果报 1033 / 无法连接,通常是 DNS 那条 CNAME 没生效、或者隧道进程没起来。先在本地 cloudflared tunnel info blog-home 看隧道是否健康,再看 Cloudflare 后台的 DNS 记录里那条 CNAME 是否存在且是橙色云。

安全:隧道通了之后要做的事

隧道最大的好处是"不暴露端口",但这不代表可以什么都不管。有几点必须做:

一、给管理后台加一层身份验证。Cloudflare 提供 Zero Trust 的 Access 功能,可以给你的网盘、监控面板这类敏感服务套一层登录(邮箱验证码、Google 登录、或者一次性 PIN)。配置方式是 Zero Trust 后台里创建一个 Access Application 绑定到 pan.example.com,加一条策略比如"只允许我的邮箱访问"。这样即使别人知道了你的域名,也会先被 Cloudflare 拦在登录页,压根到不了你的内网服务。

二、不要用隧道暴露真正危险的服务。比如直接暴露 Docker socket、数据库端口(3306/27017/6379)、或者没设密码的管理面板,即使有 Access 也要谨慎——Access 拦的是 HTTP 层,如果你把 ssh.example.com 映射出去而又允许所有人,那就是把内网的 SSH 暴露给了全网。SSH 走隧道时,配合 ~/.ssh/config 里的 ProxyCommand cloudflared access ssh --hostname ssh.example.com,再在 Access 里限制只有你自己的设备能连。

三、留意 Cloudflare 的日志。Cloudflare 后台的 Analytics 能看到所有经过隧道的请求量、状态码和来源国家。如果你发现某个私有域名有大量陌生的 404/403,说明有人在探测,可以顺手在 WAF 里加规则拦掉。

常见运维问题

cloudflared 掉线后自动恢复吗?会。它会自动重连,systemd 的 Restart=on-failure 兜底,Docker 的 --restart unless-stopped 也一样。但建议在监控里加一条检查:cloudflared tunnel info <名字> 的输出里如果 conns 为空就是掉线了。更简单的办法是从外部 curl -I https://blog.example.com 看状态码,这检查的是端到端的真实可用性。

能跑几条隧道?一个 cloudflared 进程通常跑一条隧道,一条隧道可以配多个 hostname(就是上面 ingress 里写多几条)。如果你有多个不同的机器要暴露,每台跑一个 cloudflared、各自创建一条隧道即可,它们互不影响。同一个隧道也可以在多台机器上跑多个 cloudflared 实例(replica)来做冗余,Cloudflare 会在多个连接间负载均衡,其中一台挂了自动切换。

带宽和延迟怎么样?Cloudflare 会自动把请求送到离访问者最近的边缘节点,然后通过 Cloudflare 的骨干网回到离你内网最近的入口。对静态站点体验很好。但它毕竟是中转,大文件下载会比直连慢一些,也不适合做实时性要求高的应用(比如自建游戏服务器)。另外家里宽带的上传带宽通常远小于下载,网盘的下载速度实际上是被你家宽带上行限制的,这一点要有预期。

和 frp / WireGuard 怎么选?如果你已经有公网服务器、并且想完全掌控中转链路,frp 更自由、性能更直接。如果你不想维护公网证书和 frps 服务端,或者想要 Cloudflare 顺带的 DDoS 防护和 Access 身份验证,Cloudflare Tunnel 更省心。WireGuard 则是另一类场景——它做的是"把两台机器连成一个虚拟内网",适合点对点访问和内网互访,而不是"把服务暴露成公开网站"。三者可以共存:用 WireGuard 连办公网,用 Tunnel 对外发布网站,用 frp 做特定端口的转发。

小结

Cloudflare Tunnel 用一条出站连接替代了"公网 IP + 端口转发 + 证书 + 防火墙"这一整套传统内网穿透的繁琐工作。对没有公网 IP 的个人站长来说,它是把家里或办公室的机器变成一台真正可用 Web 服务器的最低成本方案。配上 Zero Trust Access,敏感服务还能再多一层登录保护。要记住的三件事:域名必须托管在 Cloudflare;ingress 规则必须有一条兜底否则不启动;Docker 场景下目标地址不能写 localhost。把配置文件从 /root 挪到 /etc/cloudflared 并改好属主,是让 systemd 服务稳定运行的关键一步。

Last modification:September 30th, 2026 at 10:26 pm

Leave a Comment