9.2 Cloudflare Tunnel:不需要公開 IP 與開 Port 的架設#
本節目標#
- 理解 Tunnel 的反向連線原理:為什麼沒有公開 IP、沒有 Port Forwarding 也能對外服務
- 學會完整流程:安裝 cloudflared、login、create tunnel、route dns、撰寫多服務 ingress 的
config.yml - 實作:在 Linux VM 與 Windows 上的兩種安裝方式,並確認防火牆不需要任何入站規則
歷史與理論:NAT 困境與「連出即解」#
1990 年代的網際網路設計假設每台主機都有全球唯一 IP。IPv4 位址枯竭(IANA 於 2011 年分光最後一個 /8)後,NAT 成為常態:家用寬頻底下所有裝置共用一個動態位址,外部無法主動連入。傳統解法有兩條苦路:
- Port Forwarding:要 ISP 給你真實 IP、要在路由器上開洞、要自己扛下所有掃描攻擊
- 反向代理 VPN(ngrok、frp):租一台有公網 IP 的機器當跳板,成本與維運自負
Cloudflare Tunnel(原名 Argo Tunnel, 2018;2021 免費化)的洞察是:
既然家裡的 cloudflared 本來就會連出(egress)到 Cloudflare 邊緣,那就讓回程流量沿著這條已建立的通道倒著回來。
傳統架構(開洞) Tunnel(全連出)
使用者 ──▶ (A 記錄指向家用 IP) 使用者 ──▶ Cloudflare 邊緣
──▶ 撥號 NAT :4443 │ (邊緣已持有
──▶ 路由器 DNAT │ cloudflared
──▶ 家用伺服器 :443 ▼ 的 4 條連出)
↑ 開 Port=攻擊面 家用伺服器 ──▶ 邊緣 (egress)
回應 ──▶ 沿既有連線返回邊緣
cloudflared 啟動時建立 4 條 (2 個 pop × 2 個連線) 的 QUIC/HTTP2 連出連線到 Cloudflare 邊緣,並在邊緣註冊自己。此後:
從你的路由器角度看:只有 cloudflared 向外的 TCP/UDP 連線,沒有任何被動接受的(LISTEN+接conn)外部連入。防火牆入站規則為零。
安全性紅利:這條連線是 cloudflared 主動建立的,邊緣與 cloudflared 之間有 mTLS 雙向認證——攻擊者就算掃到你家 IP,裡面也沒有任何開著的門。
安裝 cloudflared#
Linux VM(Debian/Ubuntu)#
# 加入官方儲存庫(2025 起建議 pkg.cloudflareclient.com)
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-get update && sudo apt-get install -y cloudflared
cloudflared --version
Windows(範例:在 VM 或實機)#
從 GitHub Release 下載 cloudflared-windows-amd64.exe,改名 cloudflared.exe 放入 C:\Program Files (x86)\cloudflared\,之後以系統管理員執行:
cloudflared.exe service install # 註冊為 Windows 服務(重開機自動執行)
實戰:從零建一條多服務 Tunnel#
步驟 1:登入與授權#
cloudflared tunnel login
# 會開啟瀏覽器 → 選擇你的 Zone → 下載 cert.pem(帳號層級憑證,放在 ~/.cloudflared/)
步驟 2:建立 Tunnel#
cloudflared tunnel create home-lab
# 輸出:
# Created tunnel home-lab with id 8a1b-xxxx-xxxx
# 憑證寫入 ~/.cloudflared/8a1b-xxxx.json ← 這是 tunnel 專屬祕密,勿外流
cloudflared tunnel list # 確認
步驟 3:綁定 DNS(route dns)#
cloudflared tunnel route dns home-lab home.example.tk
# 自動在該 Zone 建一條 CNAME:
# home.example.tk → 8a1b-xxxx.cfargotunnel.com
# cfargotunnel.com 是 Cloudflare 維護的隧道入口,正是反向連線的公開端點
步驟 4:撰寫 ingress config.yml(多服務單 Tunnel)#
一台伺服器通常有數個服務(Nginx 的 :80、Jellyfin 的 :8096、Postgres 的 :5432 只內部使用)。用 ~/.cloudflared/config.yml 宣告一條 tunnel、多個 hostname → 本機 port的映射:
tunnel: 8a1b-xxxx-xxxx
credentials-file: /home/user/.cloudflared/8a1b-xxxx.json
ingress:
# 規則由上而下依序比對,第一個符合者生效
- hostname: home.example.tk
service: http://localhost:80 # 對應本機 Nginx(可再轉發多個 web app)
- hostname: media.example.tk
service: http://localhost:8096 # Jellyfin
- hostname: db.example.tk # 範例:只給你自己用的 DB 管理介面
service: http://localhost:8080
# 最後一定要有 catch-all;不寫會直接報錯
- service: http_status:404
# 可選:把無法用 HTTP 表示的服務(SMB、SSH)用 originRequest 包
originRequest:
connectTimeout: 5s
noTLSVerify: true # 若後端是自簽 https 服務可暫時略過憑證驗證;http:80 則不需要
httpHostHeader: home.example.tk
步驟 5:啟動與驗證#
# 前景測試
cloudflared tunnel --config ~/.cloudflared/config.yml run home-lab
# 正式:註冊為 systemd 服務,重開機自動啟動、斷線自動重連
sudo cloudflared service install
sudo systemctl enable --now cloudflared
sudo systemctl status cloudflared
cloudflared tunnel info home-lab # 查看連線數與流量
從外面一台手機(關掉 Wi-Fi)打開 https://home.example.tk——應該可以看到本機 Nginx 歡迎頁。同時確認你的路由器:沒有新增任何入站規則,這正是反向連線的物理證明。
Linux 進階:非 HTTP 的 TCP 服務(SSH、Postgres)也可以走 Tunnel:在 ingress 中用
tcp://localhost:22,然後客戶端用cloudflared access tcp --hostname ssh.example.tk --url localhost:2222建立本地隧道再連 127.0.0.1:2222。9.3 節將加上身份驗證使它更安全。
容器化模板(docker-compose)#
services:
cloudflared:
image: cloudflare/cloudflared:latest
restart: unless-stopped
command: tunnel run --token <你的 Token> home-lab # 2024 起也支援 token 模式
network_mode: host # 让 cloudflared 直接打到本機所有服務
--token 模式(遠端管理)的好處是:ingress 規則可以直接在 Cloudflare Dashboard 上的 Zero Trust → Tunnels 頁面編輯,與 9.3 的 Access 整合最順。
小結與練習#
- Tunnel 的本質是利用 cloudflared 的連出連線做反向代理:邊緣是公開端點,家用機只連出、不接受連入
- 四個 CLI 步驟口訣:
login → create → route dns → run(-service install) config.yml的 ingress 把一台伺服器的多個服務並聯到邊緣的不同 hostname 上,catch-all 是必填的最後守衛- 真正的安全性依賴「邊緣是唯一的入口」:這就是下一步 9.3 節要加上身分驗證的原因
練習:
- 搭一台 Nginx 與一個 Python HTTP 服務,用一條 tunnel 的兩個 hostname 分別對外。
- 嘗試把
config.yml中 catch-all 刪除並執行,觀察錯誤訊息,解釋為什麼設計上必須有 catch-all。 - 比較
--config.yml(本機管理)與--token(遠端管理)兩種模式的適用場景,列出各自的遷移風險。