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

Cloudflare Tunnel 設定:不開放 80 和 443

在 VPS 設定 Cloudflare Tunnel,建立具名通道、credentials 檔案與 ingress 規則,加入 systemd 服務,關閉 80 和 443,並將應用程式綁定 localhost。

Cloudflare Tunnel 的作用,以及真正的無公開連接埠代表什麼

Cloudflare Tunnel 會在 VPS 上執行名為 cloudflared 的小型 daemon。此 daemon 會向外連線至 Cloudflare,並維持連線。針對主機名稱的請求會先抵達 Cloudflare 的 edge,再透過這條既有連線傳回伺服器,因此不需要向伺服器建立傳入連線。

多數指南都不會說明的關鍵步驟是:安裝 tunnel 不會自動關閉任何連接埠。如果防火牆仍開放 80 和 443,而應用程式仍監聽 0.0.0.0,你只是增加了另一個進入途徑,並未取代原有途徑。你的 origin IP 仍可直接連線,任何找到它的人都能繞過 Cloudflare。關閉這些連接埠必須手動完成,而這個步驟才是前述設定真正有價值的原因。

cloudflared 需要透過 port 7844 對 region1.v2.argotunnel.comregion2.v2.argotunnel.com 建立 outbound 連線。它會使用 UDP 傳輸 QUIC 協定,並在必要時改用 TCP 傳輸 HTTP/2。在有 egress filtering 的網路中,請同時允許這兩種流量,或使用 --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 發行版本提供 .deb 套件,因此安裝只需下載一次並執行一次 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 位元 ARM 上,檔名結尾會是 arm64,而不是 amd64,其他部分不變。只要 cloudflared --version 輸出版本字串,即可確認安裝成功,然後繼續下一步。

以這種方式安裝的套件不在 apt 的更新流程中,因此 apt-get upgrade 不會更新它,後續更新必須自行處理。sudo cloudflared update 會取得最新版本,並直接取代現有的二進位檔;服務建立後,接著執行 sudo systemctl restart cloudflared,讓執行中的程序使用新的二進位檔。請將這項工作排入與其他修補作業相同的排程,因為 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,然後建立新的通道。

將 credentials 檔案存放在適當位置

此服務以 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,因此仍可正常運作。輸出內容應包含 tunnel 名稱、其 UUID,以及目前持有的連線數量。

撰寫含有實際 ingress 規則的 config.yml

請將設定檔寫入 /etc/cloudflared/config.yml,不要放在家目錄。原因如下。cloudflared service install 會將找到的設定檔複製到 /etc/cloudflared/config.yml,然後將 --config /etc/cloudflared/config.yml 直接寫入 systemd unit。若您在 ~/.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 的規則會符合所有 hostname,因此 catch-all 必須放在最後。如果省略它,設定檔會因 The last ingress rule must match all URLs (i.e. it should not have a hostname or path filter) 而遭拒。http_status:404 是內建服務,會回應 404,除此之外不執行任何操作。保留它有實際用途:如果沒有這項規則,對於您從未打算發布的 hostname 所發出的請求,會落到最後一個實際規則。

請在 service: URL 中使用 127.0.0.1,不要使用 localhost。在 Ubuntu 上,localhost 會先解析為 ::1,而只繫結至 IPv4 loopback 的應用程式會拒絕該連線。日誌中的訊息是 dial tcp [::1]:8080: connect: connection refused,訪客會看到 502。

此處使用純 http:// 是正確做法,因為這段連線不會離開本機。只有在本機應用程式要求使用 TLS(transport layer security)時,才使用 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,並輸出第一個符合該 URL 的規則。這是確認 path regex 沒有如預期比對的最快方式。

將 DNS 指向 tunnel

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

每次呼叫都會寫入一筆 proxied 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.

該現有記錄幾乎都是舊的 A 記錄,指向 VPS 的公開 IP;這正是需要刪除的記錄。在 Cloudflare 儀表板中刪除該記錄,然後再次執行命令。保留該記錄會使 DNS 持續公開來源 IP,因此 tunnel 不會隱藏任何資訊。

將其安裝為服務,讓它在重新開機後持續運作

先在前景執行一次,因為錯誤直接顯示在自己的終端機上,比寫入 journal 更容易閱讀。

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

正常啟動時會記錄數行 Registered tunnel connection,每個 edge location 各一行,且各自包含 connIndex。在瀏覽器中開啟其中一個主機名稱,確認 ingress 規則將請求轉送到預期位置,然後以 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,因為它會在重新開機後讓 tunnel 恢復運作。該 unit 的 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: 簡寫,而不是 named-tunnel 所需的金鑰;這種簡寫無法作為服務執行。cloudflared service is already installed 表示舊的 unit 仍然存在,因此請先執行 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,否則 tunnel 只是額外的路徑

需要進行兩項變更,而且兩項都必須完成。只做其中一項,origin 仍然可以連線。

首先,將 app 綁定至 loopback 位址。在 nginx 中,這表示以 listen 127.0.0.1:8080; 取代 listen 80;,相同的修改步驟請參閱 nginx reverse proxy 設定說明。在 Docker Compose 中,這表示 ports: - "127.0.0.1:8080:80"。一般的 "8080:80" 形式會在所有介面上發布連接埠,而且 Docker 會自行寫入 NAT(network address translation)規則。封包會先套用這些規則,之後才會經過 ufw,因此 ufw deny 規則無法阻止連線。這個陷阱另有說明:為什麼 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 firewall 基礎指南。多數 VPS 供應商也會在控制面板中提供獨立的 network firewall,該防火牆不是 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

對 raw IP 的 nc 顯示 connection refused 或逾時,且透過 hostname 得到 200,才是你要的結果。更多測試方式請參閱 確認連接埠是否真的開放。

tunnel 是傳輸機制,不是驗證機制。除非在前方加入登入機制,否則透過它發布的任何內容都可由公開網路存取。你可以在邊界使用 Cloudflare Access,或在主機上使用 置於 app 前方的 OAuth2 proxy。SSH 也需要獨立處理,因為 tunnel 不涵蓋 SSH:保留 22 開放,但限制為只接受你自己的來源位址。

Cloudflare Tunnel 的優點與成本

Cloudflare Tunnel 帶來的效益確實存在。您的 origin IP 不再公開,也不會暴露任何入站連接埠。即使機器完全沒有公開 IP,仍可完成設定。公開憑證由 Cloudflare 處理,因此您的主機不必執行 ACME(automatic certificate management environment)client。大量流量攻擊也會在邊緣節點吸收,不會消耗您的頻寬配額。

成本同樣確實存在。Cloudflare 會在其邊緣節點終止 TLS。訪客的請求會在該處解密,再重新加密後送入 tunnel,因此 Cloudflare 可以讀取流量。Cloudflare 的 firewall、caching 和 Access 規則正是依賴這項能力。只要繼續使用 Cloudflare proxy,就沒有任何設定可以停用它。如果無法接受第三方持有明文內容,請在此停止,改用其他方案。

Cloudflare 也會成為連線能力的硬性相依項目。當 cloudflared 未連線時,訪客會看到 Cloudflare 的 Error 1033 頁面,而不是您的應用程式。您也已經刻意移除了訪客原本可以改用的直接路徑。

一般瀏覽器只能透過公開 hostname 存取 HTTP、HTTPS 和 WebSocket。其他 TCP protocol,例如 SSH、RDP(remote desktop protocol)或 game server,還需要在 client 端安裝軟體:由 cloudflared access tcp 轉送本機連接埠,或使用 WARP client。這些 protocol 沒有不需安裝 client 的路徑。

Request body 在邊緣節點會受到大小上限限制。超過上限的 upload 會在抵達您的應用程式前,先以 HTTP 413 拒絕。截至 August 2026,free 和 Pro plan 的上限為 100 MB,付費 tier 的上限較高。因此,在依據特定數值設計前,請先查閱 Cloudflare 目前的 limits page。Cloudflare 的 self-serve terms 也限制主要使用 proxy 提供 video 和其他大型非 HTML 檔案。將 media library 指向 free tunnel 前,應先閱讀這些條款。

Cloudflare Tunnel、反向 SSH tunnel 或 Tailscale Funnel

這 3 種方式都只建立對外連線,因此即使主機沒有入站連接埠和公開 IP,也都能運作。它們的差異在於誰持有明文,以及公開端看到的是哪個主機名稱。

反向 SSH tunnel 需要另一台具備公開 IP 的機器,並由該機器作為對外入口。該機器上的憑證、反向代理與防火牆都由你自行管理。任何其他人都不會解密流量。這種方式涉及較多元件,而且需要搭配 autossh 或 systemd unit,並在其中設定 Restart=always,才能在網路短暫中斷後恢復運作。CGNAT 的反向 SSH tunnel 操作指南詳細說明了建置方式。

Tailscale Funnel 是最接近的比較對象。它同樣只建立對外連線,且 TLS termination 在你自己的機器上完成,因此 Tailscale 的 relay 不會看到明文。代價在於命名方式與連接埠:Funnel 只能提供 tailnet 的 ts.net 網域下的名稱,而且只能使用 443、8443 和 10000。Tailscale Serve 與 Funnel 的差異涵蓋這兩個面向。

因此,請根據真正限制你的條件來選擇。若公開使用者必須連線到你自己的網域,而且你能接受 Cloudflare 讀取流量,請選擇 Cloudflare Tunnel。若可接受 ts.net 主機名稱,且不希望將明文交給 proxy,請選擇 Tailscale Funnel。若你已經擁有一台公開機器,並且完全不希望第三方位於連線路徑中,請選擇反向 SSH tunnel。

FAQ

使用 Cloudflare Tunnel 時,仍需要開放連接埠 443 嗎?

不需要。cloudflared 會透過連接埠 7844 向外連線至 Cloudflare,所有請求都會沿這條連線返回,因此不會使用入站連接埠。不過,安裝 tunnel 不會替你關閉任何連接埠。請刪除 ufw 中允許 80 和 443 的規則,在供應商獨立的網路防火牆中關閉這些連接埠,將應用程式繫結至 127.0.0.1,並刪除仍公開 VPS IP 的任何遺留 A record。使用 sudo ss -lntp 在伺服器上確認,再從另一台機器執行 nc -vz <your-ip> 443

為什麼我的主機名稱顯示 Cloudflare Error 1033?

Error 1033 表示 Cloudflare 持有該主機名稱的 DNS record,但找不到健康且已連線的 cloudflared 來接收請求。可能是程序已停止,也可能是程序仍在執行但無法連線至 Cloudflare。檢查 systemctl status cloudflaredjournalctl -u cloudflared -n 50,然後確認 UDP 和 TCP 的出站連接埠 7844 都已允許,因為封鎖 UDP 和 QUIC、又未允許 TCP fallback 的防火牆,正會造成這種結果。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 通訊的 origin 上寫了 https://

我可以透過 Cloudflare Tunnel 執行 SSH、RDP 或遊戲伺服器嗎?

無法由一般用戶端直接執行。透過 tunnel 的公開主機名稱承載 HTTP、HTTPS 和 WebSocket,這些是瀏覽器使用的通訊協定。其他 TCP 通訊協定也需要在用戶端機器上安裝軟體,使用 cloudflared access tcp 轉送本機連接埠,或使用 WARP client。如果你想從未安裝任何軟體的任意機器建立 SSH 連線,tunnel 就不是適合的工具。請保持連接埠 22 開放,並依來源位址限制存取。

Cloudflare 看得到我透過 tunnel 傳送的網路流量嗎?

看得到。Cloudflare 會在其 edge 終止 TLS,於該處解密請求,再將請求重新加密並傳送至你伺服器上的 tunnel。這項解密是其防火牆、快取和 Access 政策能夠運作的原因,也表示你的明文資料會存在於其機器上。在使用其 proxy 的情況下,沒有任何設定可以避免這點。如果無法接受,請使用 Tailscale Funnel,或在公開伺服器上自行執行 reverse proxy。

#cloudflare-tunnel#cloudflared#zero-trust#firewall#self-hosting