約 5 分鐘

Cloudflare Tunnel 還是開 Port?自架服務對外的兩種路線

不用公網 IP、不開防火牆洞,Cloudflare Tunnel 把自架服務接上網際網路。這篇比較它與傳統 port forwarding 的安全性、延遲、成本與限制,並給出混合架構建議。

  • #Cloudflare
  • #自架
  • #反向代理
  • #網路安全
  • #Tunnel

自架服務做好之後,下一個問題永遠是同一個:要怎麼從外面連進來?

兩條路線:開 port(傳統做法)或 Cloudflare Tunnel(這幾年愈來愈多人用)。這篇不是叫你選邊,而是把兩者的攻擊面、延遲、限制講清楚,因為它們適合的東西不一樣。


一、兩種架構長什麼樣

路線 A:開 Port(Port Forwarding)

Internet → 你的公網 IP:443 → 路由器 NAT → 主機 nginx → 服務

你要的東西:公網 IP(或 DDNS)、路由器 port forward、憑證(Let’s Encrypt)、防火牆規則、以及一堆因為「機器直接暴露在掃描器面前」而來的維運工作。

路線 B:Cloudflare Tunnel

服務 ← cloudflared(主機上)→ 主動 outbound 連線 → Cloudflare Edge ← 使用者

cloudflared 在你的機器上建立對外的連線(通常是 QUIC/HTTP2 到 Cloudflare),Cloudflare 把請求送進這條連線。你不需要公網 IP、不需要開任何 inbound port。

對路由器來說,這台機器就只是「有對外連線的普通電腦」,跟瀏覽 YouTube 沒兩樣。


二、安全性:差別不在加密,在攻擊面

兩者都可以做到全程 TLS。真正的差別是誰看得到你的機器。

開 Port Cloudflare Tunnel
需要公網 IP 是(或 DDNS) 不需要
Inbound port 至少 1 個 0
誰能掃到你的 nginx 全世界 沒人(掃不到)
nginx 0-day 的風險 直接暴露 需先攻破 Cloudflare 才能觸及
DDoS 防護 自己扛 內建
憑證管理 自己續(certbot) Cloudflare 邊緣處理
真實 IP 隱藏 否 是
設定錯誤的後果 服務直接裸奔 服務公開(但無 port 可打)

開 port 的真實成本不是「設定的麻煩」,是「你的 nginx 從上線那一刻起就被人掃」。上線 24 小時內就會有針對 /wp-login.php、/.env、/actuator/env 的請求出現,這不是針對你,是背景噪音。

Tunnel 把這層噪音完全消掉,因為沒有 port 可以連。這是它最大的價值。

但有個重要的誤解要拆掉

Tunnel ≠ 有認證。 它只移除「網路層暴露」,不會幫你加登入。

如果你把 Grafana、Portainer、Home Assistant 後台直接掛上 tunnel 而沒有設 Access 政策,那個介面就是公開在網路上,任何人都能打開。只是他打不到你的 nginx 而已。

所以正確做法是兩個一起用:

公開內容(部落格、官網)  → Tunnel,不設 Access
管理介面(HA、Grafana)   → Tunnel + Cloudflare Access(Email OTP / Google 登入)

三、延遲:實務上會被感覺到嗎

架構上多了一跳:使用者 → Cloudflare 邊區節點 → 你的機器。聽起來多一次往返,但實際上:

  • Cloudflare 節點通常在離使用者 10–30ms 內
  • 你原本的架構是 使用者 → 你的老家(可能 20–40ms)
  • 淨差異通常是 +5 到 +20ms

對網頁、API、SSH、WebSocket 幾乎無感。對競技型遊戲、VoIP 會有感——但那些本來就不該走 tunnel。

真正的延遲殺手不是 tunnel,是你家上傳頻寬。 中華電信 100/40 的方案,使用者下載你的檔案時上限就是 40Mbps,這一點兩條路線完全一樣。


四、Tunnel 的限制(先知道再決定)

限制 說明 影響
主要支援 HTTP(S) TCP 服務(SSH、MySQL)需透過 cloudflared access 用戶端,不是隨便就能連 遠端桌面、DB 連線要另外處理
UDP 支援有限 別指望拿來跑遊戲伺服器或 WireGuard 需要 UDP 就還是要開 port
依賴 Cloudflare 帳號被封、政策變更、服務中斷 → 你的服務也跟著斷 重要服務要有備援
流量經過第三方 TLS 終止在邊緣,理論上 Cloudflare 看得到明文 極敏感資料建議端對端加密或不走 tunnel
大檔案 / 長連線 有連線與逾時限制 影音串流還是直連比較好
免費方案沒有 SLA 商業用途要評估 個人自架足夠

最後一點要講清楚:Tunnel 不是「零成本又完美」的方案,它是「用依賴換取簡單與安全」。你仍然把一個關鍵環節交給了第三方——這跟 PaaS 的取捨是同一件事,只是交出去的東西不一樣。


五、我建議的混合架構

不要全站選一邊。按「誰要能存取」分層:

第 0 層:靜態網站

直接丟 Cloudflare Pages,連自家的機器都不用。(本部落格就是這樣。)

第 1 層:要給所有人看的動態服務

Tunnel + 不設 Access。例如公開的 API、部落格後台以外的前台。

第 2 層:只有自己要用的管理介面

Tunnel + Cloudflare Access,限定自己的 email。Home Assistant、Grafana、Portainer、資料庫管理工具全放這裡。

第 3 層:真的需要 UDP / 非 HTTP

才開 port,而且用 DOCKER-USER 或 ufw 限制來源 IP。


六、實作:Tunnel 最小可用設定

1. 安裝

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

2. 登入並建立 tunnel

cloudflared tunnel login
cloudflared tunnel create blog

會產生 ~/.cloudflared/<UUID>.json(憑證),這個檔案不要進 git。

3. 設定檔

# /etc/cloudflared/config.yml
tunnel: <UUID>
credentials-file: /root/.cloudflared/<UUID>.json

ingress:
  - hostname: blog.example.com
    service: http://127.0.0.1:8080
  - hostname: ha.example.com
    service: http://127.0.0.1:8123
  - service: http_status:404

最後那行 http_status:404 是必要的 catch-all,少了它 cloudflared 不會啟動。

4. DNS 與服務

cloudflared tunnel route dns blog blog.example.com
sudo cloudflared service install
sudo systemctl enable --now cloudflared

5. 驗證

sudo systemctl status cloudflared
cloudflared tunnel info blog        # 應該看到 1 條以上連線
curl -I https://blog.example.com

關鍵檢查:從外部掃你的公網 IP 的 8080,應該是 closed。 如果還開著,代表你舊的 port forward 沒拆掉,等於同時開了兩條路。


七、五個常見錯誤

  1. 用 cloudflared tunnel --url localhost:8080 當正式服務——那是臨時 tunnel,重啟就換網址,只能拿來測試。
  2. 掛上管理介面卻忘了設 Access 政策——服務等於公開。設完務必用無痕視窗驗證「沒登入進不去」。
  3. 只綁 0.0.0.0 又同時保留 port forward——你以為搬家了,其實舊門還開著。
  4. 把憑證檔 commit 進 repo——那把 JSON 等於你 tunnel 的鑰匙。
  5. 沒有監控 tunnel 本身——cloudflared 掛掉時,你的網站是 502/1033,但你人在外面不知道。用 Uptime Kuma 從外部監測。

八、結論

你的情況 選
只有動態 IP、不想碰路由器 Tunnel
要給公眾存取的網站/API Tunnel(或 Pages)
只有自己用的管理介面 Tunnel + Access
需要 UDP、遊戲伺服器、極低延遲 開 Port
極度敏感資料、不想經過第三方 開 Port + 自管憑證
靜態網站 Cloudflare Pages,兩者都不用

一句話:能用 Tunnel 就用 Tunnel,需要 UDP 才開 port。 而無論選哪條,管理介面都該有第二道鎖——網路層的隱藏不是認證,只是讓你不那麼容易被找到。

最後更新:2026年10月18日