要讓外面連到你的服務,先問誰要用,再決定用 Cloudflare Tunnel 還是 SSH 通道
開發網站時常常遇到同一個問題:服務跑在自己的電腦或內網機器上,要怎麼讓外面的人或服務連得到?
可能是要給客戶看開發中的畫面、讓 Stripe 的 webhook 打到本機、用手機測試 PWA,也可能是把家裡 NAS 上的小工具公開出去,或者反過來,從自己的電腦連到正式環境的資料庫。
能解決這些問題的工具不少:Cloudflare Tunnel、SSH 通道、Tailscale、ngrok、frp……它們功能有重疊,但擅長的情境不同。這篇先說明最常用的兩種(Cloudflare Tunnel 與 SSH 通道)的原理,再從使用情境出發,一個一個說明該選哪一種、怎麼設定。
文中的網域一律用 example.com,請換成自己託管在 Cloudflare 上的網域。
一、Cloudflare Tunnel 是什麼
Cloudflare Tunnel 是 Cloudflare 提供的反向通道服務。在自己的機器上執行一個叫 cloudflared 的小程式,它會主動往外連到 Cloudflare 的邊緣節點,建立幾條常駐的連線。之後有人連到你的網域,請求先到 Cloudflare,再沿著這幾條連線送回你的機器。
flowchart LR
U["使用者瀏覽器"] -- "HTTPS<br>app.example.com" --> E["Cloudflare 邊緣節點<br>(CDN、WAF、DDoS 防護)"]
C["cloudflared<br>(你的機器)"] == "主動連出的通道" ==> E
E -. "請求沿通道送回" .-> C
C --> S["本機服務<br>localhost:8080"]
連線方向是從內部往外,所以防火牆不用開任何 port。
這個設計帶來幾個好處:
- 不需要公開 IP,也不用設定 port forwarding。在 NAT、公司內網、電信業者共用 IP(CGNAT)後面都能用。
- 真實 IP 不會曝光,外部看到的只有 Cloudflare 的 IP。
- HTTPS 憑證由 Cloudflare 處理,不用自己跑 Let’s Encrypt。
- 流量會經過 Cloudflare 的 CDN、WAF 和 DDoS 防護,也可以加上 Cloudflare Access 做登入驗證。
- 免費。
代價是:網域的 DNS 要託管在 Cloudflare,而且 TLS 在 Cloudflare 端解密,Cloudflare 看得到明文流量。
二、SSH 通道的三種模式
SSH 通道是 OpenSSH 內建的連接埠轉發功能,任何有 SSH 的機器都能用,不依賴任何服務商。有三種模式:
| 模式 | 指令 | 用途 |
|---|---|---|
本地轉發 -L |
ssh -L 5432:localhost:5432 user@server |
把遠端的服務(例如資料庫)拉到本機使用 |
遠端轉發 -R |
ssh -R 8080:localhost:3000 user@vps |
把本機的服務送到遠端主機上公開 |
動態轉發 -D |
ssh -D 1080 user@server |
建立 SOCKS proxy,流量經由遠端主機出去 |
flowchart LR
subgraph L["-L 本地轉發:把遠端拉進來"]
direction LR
A1["本機程式<br>連 localhost:5432"] --> A2["ssh 用戶端"] == "SSH 加密" ==> A3["遠端 sshd"] --> A4["遠端資料庫<br>:5432"]
end
subgraph R["-R 遠端轉發:把本機推出去"]
direction LR
B1["外部使用者<br>連 vps:8080"] --> B2["VPS 上的 sshd"] == "SSH 加密" ==> B3["本機 ssh 用戶端"] --> B4["本機服務<br>:3000"]
end
-L 和 -R 的差別在於「誰在聽」:-L 由本機監聽,-R 由遠端監聽。
和 Cloudflare Tunnel 功能重疊的是 -R:兩者都是從內部連出去,把本機服務公開出去。但 -R 需要一台有公開 IP 的跳板機或 VPS,sshd 要開 GatewayPorts yes,要網域和 HTTPS 還得在 VPS 上自己架 nginx 和 certbot,斷線要靠 autossh 重連。
三、兩者比較
| 項目 | SSH 通道 | Cloudflare Tunnel |
|---|---|---|
| 需要的基礎設施 | 一台有公開 IP 的 VPS 或跳板機 | 不用,只要網域在 Cloudflare |
| 支援的協定 | 任何 TCP | 以 HTTP/HTTPS 為主;TCP 需要用戶端也裝 cloudflared |
| 方向 | 雙向(-L 拉進來、-R 推出去) |
主要是推出去 |
| 加密與隱私 | 點對點加密,不經過第三方 | 在 Cloudflare 解密 |
| HTTPS 憑證 | 自己架 nginx 和 certbot | 自動處理 |
| 對外 IP | VPS 的 IP 會曝光 | 只看得到 Cloudflare 的 IP |
| DDoS/WAF | 沒有 | 內建 |
| 存取控制 | SSH 金鑰(只管誰能建通道) | Cloudflare Access(SSO、email 驗證) |
| 穩定性 | 靠 autossh 重連,VPS 是單點故障 | 自動重連,同時連到多個節點 |
| 上傳限制 | 只受 VPS 頻寬限制 | 免費方案單一請求上限 100MB |
| 費用 | VPS 月費 | 免費 |
| 廠商綁定 | 沒有 | 綁定 Cloudflare |
簡單來說,SSH 通道比較底層、通用,但機器、憑證、防護都要自己準備;Cloudflare Tunnel 專門處理「把 HTTP 服務公開到網路上」這件事,附帶 HTTPS、防護和存取控制,代價是流量要經過 Cloudflare。
四、依使用情境選擇
以下每個情境依序說明:要解決什麼問題、建議的方案、為什麼選它,以及關鍵的設定。
情境 1:臨時給別人看開發中的畫面
本機跑著
localhost:3000,想讓客戶或同事看一下,看完就關掉。
建議方案:Cloudflare Quick Tunnel
不用 Cloudflare 帳號,也不用網域,只要一行指令:
# 安裝(Debian/Ubuntu)
curl -L -o cloudflared.deb \
https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb
sudo dpkg -i cloudflared.deb
# 分享本機的 3000 port
cloudflared tunnel --url http://localhost:3000
終端機會印出一個像 https://xxx-yyy-zzz.trycloudflare.com 的網址,把它傳給對方就行了。按 Ctrl+C 結束後網址就失效。
限制:每次啟動網址都不一樣、有併發請求的上限、不支援 Server-Sent Events。只適合臨時展示,不要用在正式環境。
如果不想安裝任何東西,也可以用 SSH 型的服務,概念一樣:
ssh -R 80:localhost:3000 localhost.run
情境 2:Webhook 開發、手機實機測試
開發 LINE Bot、Stripe 付款、GitHub App,對方要一個固定的 HTTPS 網址回呼;或是要在手機上測試相機、定位、PWA 這些需要 HTTPS(secure context)的功能。
建議方案:Cloudflare Named Tunnel,給一個固定的子網域,例如 dev.example.com
Quick Tunnel 的網址每次都會變,每次重開都要去 webhook 設定頁改網址,很麻煩。Named Tunnel 的網址是固定的,設定一次之後,每天開發時只要啟動 cloudflared 就能接收 webhook。設定方式見下一個情境。
如果需要檢視每個 request 的內容、重送 request 來除錯,ngrok 內建的 inspector 介面比較方便,可以考慮搭配使用。
情境 3:把 listen 某個 port 的程式部署到子網域
機器上有個程式聽 8080 port,想讓它變成
https://app.example.com,而且要長期穩定運作。
建議方案:Cloudflare Named Tunnel + systemd 常駐
這是 Cloudflare Tunnel 最典型的用法。網域已經託管在 Cloudflare 的話,第一次設定大約 5 分鐘:
# 1. 登入授權(會開瀏覽器,選擇 example.com)
cloudflared tunnel login
# 2. 建立一條 tunnel(一台機器建一條就夠,可以掛很多服務)
cloudflared tunnel create my-server
# 3. 建立 DNS:app.example.com → 這條 tunnel(自動建立 CNAME)
cloudflared tunnel route dns my-server app.example.com
# 4. 啟動
cloudflared tunnel run --url http://localhost:8080 my-server
打開 https://app.example.com 就能看到服務,HTTPS 自動生效。
一台機器掛多個服務
一條 tunnel 可以依 hostname 把請求分到不同的 port:
flowchart LR
U1["app.example.com"] --> E["Cloudflare"]
U2["api.example.com"] --> E
U3["admin.example.com"] --> E
E == "同一條 tunnel" ==> C["cloudflared<br>ingress 規則"]
C --> S1["localhost:8080<br>前端"]
C --> S2["localhost:3000<br>API"]
C --> S3["localhost:9000<br>管理後台"]
一條 tunnel、一個 cloudflared 程序,依 hostname 分流到三個服務。
把規則寫在 ~/.cloudflared/config.yml:
tunnel: my-server
credentials-file: /home/me/.cloudflared/<TUNNEL-ID>.json # 第 2 步產生的檔案
ingress:
- hostname: app.example.com
service: http://localhost:8080
- hostname: api.example.com
service: http://localhost:3000
- hostname: admin.example.com
service: http://localhost:9000
- service: http_status:404 # 最後一條必須是 catch-all
# 每個新的子網域都要建一次 DNS
cloudflared tunnel route dns my-server api.example.com
cloudflared tunnel route dns my-server admin.example.com
# 檢查設定
cloudflared tunnel ingress validate
# 註冊成系統服務,開機自動啟動
sudo cloudflared --config ~/.cloudflared/config.yml service install
sudo systemctl restart cloudflared
之後要新增服務只要三步:在 config.yml 加一條規則、執行一次 route dns、重新啟動 cloudflared。
也可以不用 CLI,改到 Cloudflare Dashboard 的 Zero Trust → Networks → Tunnels 建立 tunnel,在伺服器上執行 sudo cloudflared service install <TOKEN>,之後子網域的增減都在網頁上操作。用 Docker 的話,cloudflare/cloudflared 映像檔可以直接放進 docker-compose 當作一個服務。
常見問題
- 子網域只能一層。免費的 Universal SSL 憑證涵蓋
example.com和*.example.com,所以app.example.com沒問題,dev.app.example.com會出現憑證錯誤,要付費的 Advanced Certificate 才能用。 - DNS 記錄衝突。子網域如果已經有 A 或 CNAME 記錄,
route dns會失敗,要先刪掉舊記錄,或加-f強制覆蓋。 - 框架擋掉 Host header。很多開發伺服器預設只接受
localhost,要把網域加進白名單:- Vite:
server.allowedHosts: ['app.example.com'] - Django:
ALLOWED_HOSTS = ['app.example.com'] - Rails:
config.hosts << "app.example.com" - webpack dev server:
allowedHosts: 'all'
- Vite:
- 程式本身是 HTTPS(自簽憑證):
service改成https://localhost:8443,並加上originRequest: noTLSVerify: true - 出現 502 Bad Gateway。通常是本機服務沒有啟動或 port 寫錯。先在機器上執行
curl http://localhost:8080確認服務有回應。 - 程式讀到的使用者 IP 都是 Cloudflare 的。真實 IP 在
CF-Connecting-IPheader 裡。
情境 4:內部工具、staging 環境只給特定的人看
Grafana、Jenkins、管理後台要給同事用;staging 環境要給客戶驗收,但不能讓所有人都看得到。
建議方案:Cloudflare Tunnel + Cloudflare Access
照情境 3 把服務接上子網域後,到 Zero Trust → Access → Applications 為這個子網域建立一個應用程式,設定允許的對象,例如 @example.com 結尾的信箱、指定的幾個 email,或是 Google、GitHub 帳號登入。
sequenceDiagram
participant U as 使用者
participant A as Cloudflare Access
participant T as Tunnel
participant S as 內部工具
U->>A: 開啟 grafana.example.com
A-->>U: 尚未登入,導向登入頁
U->>A: 用 email 驗證碼或 Google 登入
A->>A: 檢查是否在允許名單內
A-->>U: 發給 session cookie
U->>A: 帶著 cookie 再次請求
A->>T: 驗證通過,轉送請求
T->>S: localhost:3000
S-->>U: 回應頁面
驗證發生在 Cloudflare,沒通過的請求根本不會進到你的機器。
好處是程式完全不用修改,也不用架 VPN 或發 SSH 金鑰給不熟技術的人。免費方案可以給 50 個使用者用。
如果是團隊成員之間要長期互連多台內網機器,而且不只用到 HTTP(例如 SSH、資料庫、檔案分享),Tailscale 這類 mesh VPN 會更直接:每台機器裝上 Tailscale 後,彼此就像在同一個區網裡。
情境 5:從本機連到遠端的資料庫
正式環境的 PostgreSQL 只聽
127.0.0.1,想用本機的 GUI 工具連進去查資料。
建議方案:SSH -L 本地轉發
這是 SSH 通道的主場。Cloudflare Tunnel 擅長「推出去」,而這個情境是「拉進來」:
ssh -N -L 15432:localhost:5432 user@db-server
然後本機的 GUI 工具連 localhost:15432 就可以了。-N 表示不開 shell,只做轉發。
常用的話可以寫進 ~/.ssh/config:
Host prod-db
HostName db-server.example.com
User deploy
LocalForward 15432 localhost:5432
之後只要執行 ssh -N prod-db。
同類的用法還有:臨時偵錯時順手加上 -L 看遠端某個只聽 localhost 的管理介面;或用 ssh -D 1080 建立 SOCKS proxy,讓瀏覽器經由遠端主機上網。
情境 6:伺服器不想開 22 port,但還是要能 SSH 進去
想把伺服器的防火牆全部關掉,連 SSH 也不對外開放。
建議方案:讓 SSH 走 Cloudflare Tunnel,再用 Access 控管
在 tunnel 的 ingress 加一條把 SSH 送進來的規則:
ingress:
- hostname: ssh.example.com
service: ssh://localhost:22
# ...其他規則
- service: http_status:404
用戶端也要安裝 cloudflared,在 ~/.ssh/config 加上:
Host ssh.example.com
ProxyCommand cloudflared access ssh --hostname %h
之後 ssh [email protected] 就會經由 Cloudflare 連進去。再為 ssh.example.com 設定 Access,連線前會先開瀏覽器做 SSO 驗證。伺服器不用對外開任何 port,而且可以從 Access 集中管理誰能登入。
這也說明兩種工具不是二選一:外層用 Cloudflare 保護入口,進去之後照樣用 SSH -L 連資料庫,兩者可以疊在一起用。
情境 7:Cloudflare Tunnel 不適合的情況
有些需求不適合讓流量經過 Cloudflare:
| 需求 | 建議方案 | 原因 |
|---|---|---|
| 流量不能讓第三方解密(金融、醫療、合約限制) | SSH 通道,或自架 frp、WireGuard | 點對點加密,中間沒有人看得到 |
| 大檔案上傳(超過 100MB)、影音串流 | SSH -R + VPS,或直接架在 VPS 上 |
避開大小上限和 Cloudflare 服務條款的限制 |
| 非 HTTP 協定要給一般使用者連(遊戲伺服器、MQTT、自訂 TCP) | 自架 frp,或 VPS 直接開 port | Cloudflare Tunnel 的 TCP 需要用戶端安裝 cloudflared |
| 對延遲極度敏感 | VPS 直接架設,或 Tailscale 直連 | 少繞一段中繼 |
| 不想被任何服務商綁定 | SSH 通道、frp | 標準開源工具,到哪裡都能用 |
五、總整理
依情境選工具
| 分類 | 情境 | 建議方案 |
|---|---|---|
| 公開 Web 服務 | 臨時給別人看 | Cloudflare Quick Tunnel |
| Webhook 開發、手機實機測試 | Cloudflare Named Tunnel(固定子網域) | |
| 小型網站、side project 正式上線 | Named Tunnel + systemd | |
| 家用伺服器、NAS、沒有固定 IP | Cloudflare Tunnel | |
| 一台機器掛多個服務 | Tunnel + config.yml ingress |
|
| 限定特定的人 | 內部工具、管理後台 | Tunnel + Access |
| staging 給客戶驗收 | Tunnel + Access(指定 email) | |
| 團隊多台機器長期互連 | Tailscale | |
| 工程師存取 | 連遠端資料庫、Redis | SSH -L |
| 臨時偵錯 | SSH -L/-R |
|
| SOCKS proxy | SSH -D |
|
| 伺服器不開 22 port | Tunnel + Access 保護 SSH | |
| 特殊需求 | 不能讓第三方解密 | SSH 通道、frp、WireGuard |
| 大檔案、影音串流 | VPS 直接架設 | |
| 非 HTTP 協定要公開 | frp、VPS 開 port | |
| 要檢視、重送 request | ngrok |
決策流程
flowchart TD
Q1{"要給「瀏覽器使用者」<br>存取嗎?"}
Q1 -- 是 --> Q2{"能接受流量<br>經過 Cloudflare 嗎?"}
Q1 -- "否,給工程師或程式" --> Q5{"要做什麼?"}
Q2 -- 否 --> X1["SSH -R + VPS<br>或自架 frp"]
Q2 -- 是 --> Q3{"只是臨時用?"}
Q3 -- 是 --> T1["Quick Tunnel"]
Q3 -- "否,要長期或固定網址" --> T2["Named Tunnel<br>app.example.com"]
T2 --> Q4{"要限制誰能看?"}
Q4 -- 是 --> T3["再加上 Cloudflare Access"]
Q4 -- 否 --> T4["直接公開"]
Q5 -- "把遠端服務拉到本機" --> S1["SSH -L"]
Q5 -- "多台機器長期互連" --> S2["Tailscale"]
Q5 -- "非 HTTP 協定要公開" --> S3["frp 或 VPS 開 port"]
先問「誰要用」,再問「能不能經過 Cloudflare」,大多數情況兩三個問題就能決定。
結語
如果網域已經託管在 Cloudflare,日常的 Web 開發和小型部署,大部分情境用 Cloudflare Named Tunnel 就能解決:一條 tunnel 掛多個子網域,需要時加上 Access 控管存取,伺服器一個 port 都不用開。剩下「拉進來」的需求,例如連遠端資料庫,交給 SSH -L。
只有在流量不能經過第三方、要傳大檔案,或要公開非 HTTP 協定時,才需要動用 VPS、frp 或 Tailscale。判斷的原則很簡單:給「人」用瀏覽器存取的服務,選 Cloudflare Tunnel;給工程師或程式存取任意協定,或流量不能經過第三方的,選 SSH 通道或自架方案。