SSD Nodes Learn Hosting plans →
指南 Matt Connor作者: Matt Connor · 更新于 2026-08-26

Cloudflare Tunnel 无需开放端口的完整配置

在 VPS 上配置 Cloudflare Tunnel:创建命名隧道、保存凭据、设置 ingress 和 systemd 服务,并关闭 80、443,仅让应用监听 localhost。

Cloudflare Tunnel 的作用,以及“没有开放端口”真正意味着什么

Cloudflare Tunnel 会在您的 VPS 上运行一个名为 cloudflared 的小型守护进程。该守护进程向 Cloudflare 发起连接并保持连接。发往您的主机名的请求会先到达 Cloudflare 边缘节点,再通过这条现有连接转发回您的服务器,因此无需向服务器建立入站连接。

大多数指南都不会说明下一步:安装 Tunnel 不会自动关闭任何端口。如果防火墙中的 80 和 443 仍处于开放状态,您的应用仍监听 0.0.0.0,那么您增加的是第二条访问路径,而不是替换原有路径。攻击者仍可访问您的源站 IP,找到该 IP 后即可绕过 Cloudflare 直接连接。关闭这些端口需要手动完成;这一步决定了前面的配置是否真正有效。

cloudflared 需要通过端口 7844 出站访问 region1.v2.argotunnel.comregion2.v2.argotunnel.com。它使用 UDP 运行 QUIC 协议,并在必要时回退到 TCP 运行 HTTP/2。在实施出站流量过滤的网络中,请同时放行这两种协议,或使用 --protocol http2 强制使用 TCP。

开始之前

  • 一个已添加到 Cloudflare 账户的域名,并且该区域使用 Cloudflare 的名称服务器。cloudflared tunnel route dns 会向该区域写入记录,因此必须先创建该区域。
  • VPS 可通过 UDP 和 TCP 访问出站端口 7844。
  • 本地已有应用在监听,即使首次测试时仅使用 python3 -m http.server 8080 也可以。
  • 服务器上已有 sudo,并且在修改防火墙前已打开第二个 SSH 会话。

在 Ubuntu 或 Debian 上安装 cloudflared

Cloudflare 会为每个 cloudflared release 提供一个 .deb package,因此安装只需下载一次并执行一次 dpkg 调用。

curl -fsSL -o /tmp/cloudflared.deb \
  https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb
sudo dpkg -i /tmp/cloudflared.deb
cloudflared --version

如果不确定主机架构,先运行 dpkg --print-architecture。在 64-bit ARM 上,文件名以 arm64 结尾,而不是 amd64;其他内容不变。只要 cloudflared --version 输出版本字符串,即可确认安装成功,然后继续下一步。

通过这种方式安装的软件不在 apt 的更新路径中,因此 apt-get upgrade 不会更新它,后续更新需要由您负责。sudo cloudflared update 会获取最新 release,并原地替换 binary;服务创建后,再执行 sudo systemctl restart cloudflared,使运行中的进程使用新的 binary。请将此操作安排到与其他补丁更新相同的周期,因为 tunnel daemon 面向互联网,即使它不会监听任何端口。

登录并创建命名隧道

cloudflared tunnel login

无头 VPS 不会打开浏览器,因此请将输出的 URL 复制到笔记本电脑上的浏览器中,然后选择区域。完成后,~/.cloudflared/cert.pem 即会生成。

cert.pem 是您的账户凭据。它可授权创建隧道、向该区域写入 DNS 记录以及删除隧道。运行中的隧道不会使用它。请像保护密码一样保护它,因为只要有人取得该文件的副本,就足以在您的域名下发布新的主机名。

cloudflared tunnel create homelab

运行成功后会输出以下两行,其中的 UUID 就是您要粘贴到配置文件中的值。

Tunnel credentials written to /home/you/.cloudflared/6ff42ae2-765d-4adf-8112-31c55c1551ef.json. cloudflared chose this file based on where your origin certificate was found. Keep this file secret. To revoke these credentials, delete the tunnel.
Created tunnel homelab with id 6ff42ae2-765d-4adf-8112-31c55c1551ef

该 JSON 文件是隧道的身份凭据,也是运行中服务唯一需要的凭据。持有该文件的任何人都可以注册为您的隧道并接收您的流量。它无法单独轮换:撤销它意味着 cloudflared tunnel delete homelab 并创建新隧道。

将凭据文件放在合适的位置

该服务以 root 身份运行,因此应将文件放在 root 所有的目录中,不要将其留在备份任务或共享登录可能访问的主目录下。

sudo install -d -m 700 -o root -g root /etc/cloudflared
sudo install -m 600 -o root -g root \
  ~/.cloudflared/6ff42ae2-765d-4adf-8112-31c55c1551ef.json \
  /etc/cloudflared/6ff42ae2-765d-4adf-8112-31c55c1551ef.json
rm ~/.cloudflared/6ff42ae2-765d-4adf-8112-31c55c1551ef.json
sudo ls -l /etc/cloudflared
cloudflared tunnel list

ls -l 应在 JSON 文件上显示 -rw------- root rootcloudflared tunnel list 会读取 cert.pem,因此仍可正常工作,并应输出隧道名称、其 UUID 以及当前保持的连接数。

编写包含实际入口规则的 config.yml

将配置写入 /etc/cloudflared/config.yml,不要写在您的主目录中。原因如下:cloudflared service install 会将找到的配置复制到 /etc/cloudflared/config.yml,然后把 --config /etc/cloudflared/config.yml 硬编码到 systemd 单元中。如果您在 ~/.cloudflared/config.yml 中创建文件,这次复制就只是一次性快照。之后对主目录中副本的任何修改都不会生效,服务会继续提供旧规则,而且不会发出任何警告。直接在目标位置创建文件可以彻底避免这个问题。

tunnel: 6ff42ae2-765d-4adf-8112-31c55c1551ef
credentials-file: /etc/cloudflared/6ff42ae2-765d-4adf-8112-31c55c1551ef.json
loglevel: info

ingress:
  - hostname: app.example.com
    service: http://127.0.0.1:8080
  - hostname: files.example.com
    service: http://127.0.0.1:8081
  - hostname: grafana.example.com
    path: ^/api/
    service: http://127.0.0.1:3000
  - service: http_status:404

规则按从上到下的顺序读取,并采用第一个匹配项。没有 hostname 的规则会匹配所有主机名,因此兜底规则必须放在最后。如果省略它,配置会因 The last ingress rule must match all URLs (i.e. it should not have a hostname or path filter) 而被拒绝。http_status:404 是一个内置服务,会返回 404,不执行其他操作。保留它很重要:否则,对您从未打算发布的主机名发起的请求会继续匹配,落到最后一个恰好存在的实际规则上。

service: URL 中使用 127.0.0.1,不要使用 localhost。在 Ubuntu 上,localhost 会优先解析为 ::1,而只绑定到 IPv4 loopback 地址的应用会拒绝该连接。日志中会出现 dial tcp [::1]:8080: connect: connection refused,访问者会看到 502。

这里使用普通的 http:// 是正确的,因为这一跳不会离开本机。只有在本地应用强制要求使用 TLS(传输层安全)时,才使用 https://;如果其证书与您连接时使用的名称不匹配,预计会出现 x509: certificate is valid for example.com, not localhost。可在 originRequest 下使用 originServerName 修复此问题,或者使用 noTLSVerify: true 接受风险。

启动任何服务前,先检查规则:

sudo cloudflared --config /etc/cloudflared/config.yml tunnel ingress validate
sudo cloudflared --config /etc/cloudflared/config.yml tunnel ingress rule https://app.example.com/login

ingress validate 会报告配置有效,或指出导致解析失败的规则。ingress rule 接受一个 URL,并输出与其匹配的第一条规则。这是确认 path 正则表达式是否匹配您预期内容的最快方法。

将 DNS 指向隧道

cloudflared tunnel route dns homelab app.example.com
cloudflared tunnel route dns homelab files.example.com
cloudflared tunnel route dns homelab grafana.example.com

每次调用都会写入一条代理 CNAME 记录,并将其指向 6ff42ae2-765d-4adf-8112-31c55c1551ef.cfargotunnel.com。该目标只能在 Cloudflare 网络内部解析,因此主机名的公共 DNS 响应会返回 Cloudflare 地址,不会暴露 VPS 的 IP。config.yml 中的通配符 hostname 仍需要为实际使用的每个名称配置对应的 DNS 记录。

如果记录已经存在,命令会显示以下错误:

Failed to add route: code: 1003, reason: Failed to create record app.example.com with err An A, AAAA, or CNAME record with that host already exists.

该现有记录几乎总是指向 VPS 公网 IP 的旧 A 记录,而这正是需要删除的记录。在 Cloudflare 控制面板中删除该记录,然后再次运行命令。保留该记录意味着 DNS 仍会发布源站 IP,因此隧道不会隐藏任何信息。

将其安装为服务,使其在重启后仍能运行

先在前台运行一次,因为配置错误在您自己的终端中比在 journal 中更容易阅读。

sudo cloudflared --config /etc/cloudflared/config.yml tunnel run homelab

正常启动时会记录多行 Registered tunnel connection,每个边缘位置一行,并且每行都有自己的 connIndex。在浏览器中打开您的一个主机名,确认入口规则将请求转发到预期位置,然后使用 Ctrl-C 停止它。

sudo cloudflared --config /etc/cloudflared/config.yml service install
systemctl status cloudflared

该命令会写入 /etc/systemd/system/cloudflared.servicecloudflared-update.servicecloudflared-update.timer,然后为您运行 systemctl enable cloudflared.servicesystemctl start cloudflared.service。这里真正重要的是 enable,因为它负责在重启后恢复隧道。该单元的 ExecStartcloudflared --no-autoupdate --config /etc/cloudflared/config.yml tunnel run,因此不能更改此配置路径。

此步骤中的 3 个失败情况会显示明确的错误消息。possible conflicting configuration in /home/you/.cloudflared/config.yml and /etc/cloudflared/config.yml 表示两个文件都存在,cloudflared 不会自行判断应使用哪个:删除不需要的文件。configuration file ... must contain entries for the tunnel to run and its associated credentials (tunnel: TUNNEL-UUID, credentials-file: CREDENTIALS-FILE) 表示配置使用了快速 url: 简写,而不是命名隧道键;该简写无法作为服务运行。cloudflared service is already installed 表示旧单元仍然存在,因此先运行 sudo cloudflared service uninstall

没有 reload 操作。编辑 /etc/cloudflared/config.yml 后,运行 sudo systemctl restart cloudflared。然后实际验证重启后的运行情况,不要只是假设它能运行:

sudo reboot
# once it is back
systemctl is-enabled cloudflared
systemctl is-active cloudflared
journalctl -u cloudflared -n 50 --no-pager
curl -sI https://app.example.com

is-enabled 输出 enabled,并且 is-active 输出 active,正是本节要验证的结果。路由已创建且服务正在运行后,cert.pem 在服务器上就不再需要执行其他工作:rm ~/.cloudflared/cert.pem。以后添加主机名,只需再次运行 cloudflared tunnel login

关闭 80 和 443,否则隧道只是额外的访问路径

需要进行两项更改,而且两项都不能少。只做其中一项,源站仍然可以直接访问。

首先,将应用绑定到回环地址。在 nginx 中,这意味着使用 listen 127.0.0.1:8080; 代替 listen 80;,具体修改步骤请参阅这篇 nginx 反向代理配置说明。在 Docker Compose 中,这意味着使用 ports: - "127.0.0.1:8080:80"。普通的 "8080:80" 形式会在所有网络接口上发布端口。Docker 会自行写入 NAT(网络地址转换)规则。数据包会先匹配这些规则,再到达 ufw,因此 ufw 拒绝规则无法阻止这些连接。这个问题另有说明:为什么 Docker 发布的端口会绕过 ufw

sudo ss -lntp

现在,每个已迁移的服务都应在 Local Address 列中显示 127.0.0.1:8080。显示 0.0.0.0:8080*:8080 的行仍在监听所有网络接口,因此公网仍可访问。

其次,关闭这些端口。

sudo ufw status numbered
sudo ufw delete allow 'Nginx Full'
sudo ufw status

删除 80 和 443 的 allow 规则,不要直接在其上叠加 deny 规则。ufw 会在匹配到第一条规则后停止处理。如果规则列表上方仍有过期的 allow 规则,它会优先生效。保留 SSH 规则。ufw 防火墙基础指南介绍了其余规则配置。大多数 VPS 提供商还会在控制面板中运行独立的网络防火墙。它不是 ufw,因此也要在那里关闭 80 和 443。

现在从其他位置验证,因为在服务器本机执行 curl http://127.0.0.1:8080 不能证明公网访问情况。

# run these from a different machine
nc -vz 203.0.113.10 443
curl -sI https://app.example.com

针对原始 IP 得到拒绝连接或超时的 nc,并且通过主机名得到 200,才是预期结果。检查端口是否真正开放介绍了更多测试方法。

隧道只负责传输,不负责身份验证。除非在前面配置登录验证,否则通过隧道发布的任何内容都可被公网访问。可以在边缘使用 Cloudflare Access,也可以在服务器上让位于应用前面的 OAuth2 proxy负责验证。SSH 也需要单独处理,因为隧道不覆盖 SSH:保持 22 端口开放,但仅允许您自己的源地址访问。

Cloudflare Tunnel 的收益与代价

它带来的收益确实存在。您的源站 IP 不再公开,也不会暴露入站端口。即使服务器完全没有公网 IP,也可以使用这种配置。公网证书由 Cloudflare 负责,因此您的服务器上无需运行 ACME(自动证书管理环境)客户端。大流量攻击会在边缘节点被吸收,不会消耗您的带宽配额。

这些代价同样确实存在。Cloudflare 会在其边缘节点终止 TLS:访问者的请求会在那里解密,然后重新加密并传入隧道,因此 Cloudflare 可以读取流量。Cloudflare 的防火墙、缓存和 Access 规则正是依赖这一点。只要继续使用其代理,就没有任何设置可以关闭此行为。如果不能接受第三方持有明文,请在这里停止,并选择其他方案。

Cloudflare 还会成为可达性的硬依赖。当 cloudflared 未连接时,访问者看到的是 Cloudflare 的 Error 1033 页面,而不是您的应用。您也已经有意移除了访问者原本可以回退使用的直连路径。

普通浏览器只能通过公网主机名访问 HTTP、HTTPS 和 WebSocket。其他 TCP 协议、SSH、RDP(远程桌面协议)或游戏服务器还需要客户端软件:cloudflared access tcp 用于转发本地端口,或者使用 WARP 客户端。这些协议没有无需客户端的访问路径。

边缘节点会限制请求正文大小。超过限制的上传会在到达您的应用前被拒绝,并返回 HTTP 413。截至 2026 年 8 月,免费版和 Pro 计划的上限为 100 MB,付费层级的上限更高。因此,在围绕某个数值设计方案前,请先查看 Cloudflare 当前的限制页面。Cloudflare 的自助服务条款还限制将代理主要用于提供视频和其他大型非 HTML 文件。将媒体库指向免费隧道前,应先阅读相关条款。

Cloudflare Tunnel、反向 SSH 隧道或 Tailscale Funnel

这三种方案都只需要出站连接,因此在没有入站端口和公网 IP 的服务器上也能工作。它们的区别在于谁能看到明文,以及公网用户看到的主机名是什么。

反向 SSH 隧道需要另一台拥有公网 IP 的服务器,这台服务器会成为入口:证书、反向代理和防火墙都由您负责运行。没有其他人会解密流量。它涉及更多组件;要让隧道在网络短暂中断后自动恢复,还需要在 autossh 或 systemd 单元中配置 Restart=alwaysCGNAT 环境下的反向 SSH 隧道配置指南介绍了完整配置过程。

Tailscale Funnel 是最接近的对比方案。它同样只需要出站连接,并且 TLS 在您自己的服务器上终止,因此 Tailscale 的中继不会看到明文。代价在于主机名和端口:Funnel 只能提供 tailnet 的 ts.net 域名下的主机名,并且只能使用 443、8443 和 10000 端口。Tailscale Serve 与 Funnel 的区别介绍了这两方面的差异。

因此,应根据真正限制您的条件进行选择。如果公网用户必须访问您自己的域名,并且您接受 Cloudflare 读取流量,请选择 Cloudflare Tunnel。如果 ts.net 主机名可以接受,并且您不希望将明文交给代理,请选择 Tailscale Funnel。如果您已经拥有一台公网服务器,并且完全不希望路径中出现第三方,请选择反向 SSH 隧道。

FAQ

使用 Cloudflare Tunnel 时,仍需要开放 443 端口吗?

不需要。cloudflared 会通过 7844 端口向外连接到 Cloudflare,所有请求都会沿该连接返回,因此无需使用入站端口。不过,安装 Tunnel 不会自动为您关闭任何端口。请删除 ufw 中允许 80 和 443 的规则,在服务提供商的独立网络防火墙中关闭这两个端口,将应用绑定到 127.0.0.1,并删除仍然发布 VPS IP 的遗留 A 记录。使用 sudo ss -lntp 在服务器上确认,再从另一台机器运行 nc -vz <your-ip> 443 进行确认。

为什么我的主机名显示 Cloudflare 错误 1033?

错误 1033 表示 Cloudflare 持有该主机名的 DNS 记录,但找不到处于健康状态、已连接并可接收请求的 cloudflared。进程可能已停止,也可能仍在运行但无法连接到 Cloudflare。检查 systemctl status cloudflaredjournalctl -u cloudflared -n 50,然后确认 UDP 和 TCP 都允许出站端口 7844,因为防火墙如果阻止 UDP 和 QUIC,且未允许 TCP 回退,就会产生此错误。cloudflared tunnel info homelab 显示 Cloudflare 当前检测到的连接。如果列表为空,问题就在您的服务器一侧。

为什么通过 Tunnel 访问时会收到 502 Bad Gateway?

502 表示已连接到 cloudflared,但无法连接到本地服务,因此问题位于这两者之间,而不在 Cloudflare。请查看日志。dial tcp [::1]:8080: connect: connection refused 表示您指定的位置没有任何进程监听,而该消息中的 [::1] 通常表示您在 service: URL 中写入了 localhost,但应用仅绑定 IPv4,因此应改为 http://127.0.0.1:8080HTTP/1.x transport connection broken: malformed HTTP response 表示相反的协议不匹配:您在使用纯 HTTP 的源站中写入了 https://

可以通过 Cloudflare Tunnel 运行 SSH、RDP 或游戏服务器吗?

不能直接从普通客户端运行。通过 Tunnel 提供的公网主机名承载 HTTP、HTTPS 和 WebSocket,这些是浏览器使用的协议。其他 TCP 协议还需要在客户端计算机上安装软件,可以使用 cloudflared access tcp 转发本地端口,或使用 WARP 客户端。如果您希望从未安装任何软件的任意机器连接 SSH,Tunnel 就不适合这种场景。请保持 22 端口开放,并按源地址限制访问。

Cloudflare 能看到通过 Tunnel 传输的流量吗?

能。Cloudflare 会在其边缘节点终止 TLS,在那里解密请求,然后将请求重新加密后传入 Tunnel,再发送到您的服务器。Cloudflare 的防火墙、缓存和 Access 策略正是依赖这一步解密才能生效;这也意味着您的明文数据会存在于其服务器上。在使用其代理时,没有任何配置可以避免这一点。如果无法接受,请使用 Tailscale Funnel,或在公网服务器上运行自己的反向代理。

#cloudflare-tunnel#cloudflared#zero-trust#firewall#自托管