從外面連回家裡的內網服務
在家裡的電腦上跑了一個 web 服務,想從外面(或另一台裝置)連回去看。 這篇記錄先用 SSH tunnel 踩到的雷、為什麼會卡,以及最後為什麼改用 Tailscale。
一、SSH tunnel:ssh -L 到底在做什麼
最直覺的做法是 SSH 埠轉發:
ssh -L 8000:localhost:8000 使用者@遠端主機這三個參數最常被誤解,拆開來看:
| 參數 | 是誰的視角 | 意思 |
|---|---|---|
8000(第一個) | 本機(你現在坐的這台) | 在本機監聽這個埠 |
localhost:8000(後兩個) | SSH server | 連線送到對面之後,對面要往哪裡轉 |
換句話說:你在本機瀏覽器打 http://localhost:8000,流量走加密隧道到遠端主機,然後由遠端主機自己去連 localhost:8000。
關鍵理解是後面那個 localhost 指的是遠端的 localhost,不是你的。
二、失敗症狀與診斷
實際跑起來,瀏覽器一直 ERR_EMPTY_RESPONSE。
診斷過程中真正有價值的是這三條指令的對比:
| 在哪裡跑 | 指令 | 結果 |
|---|---|---|
| 本機瀏覽器 | http://localhost:8000 | ❌ ERR_EMPTY_RESPONSE |
| 遠端主機 | curl http://localhost:8000/... | ❌ 連線立刻被斷,完全沒輸出 |
| 遠端主機 | curl http://<內層 IP>:8000/... | ✅ 200 OK |
第三條是診斷關鍵:服務本身是好的、遠端主機也連得到它,唯獨遠端主機的 localhost:8000 沒有通。
問題不在 SSH,在最後一哩。
除錯順序:從內往外一段一段確認
網路鏈路有好幾段的時候,不要從頭猜。先確認最裡面那層服務活著(在那台機器上直接 curl),再往外一段一段驗,最後才懷疑 SSH。這樣每次都能把問題縮小到某一段,而不是在整條鏈路上亂試。
三、為什麼會卡:WSL2 的 localhost relay
如果你的服務是跑在 Windows 底下的 WSL2 裡,這裡有個隱藏機制。
WSL2 預設用 NAT networking,它其實是一台獨立的虛擬機、有自己的 IP。Windows 之所以能用 localhost:某個埠 連到 WSL2 裡的服務,靠的是一個叫 localhost relay 的背景機制——由 WSL service 偷偷把流量導進去。
這個 relay 有兩個很有事的特性:
- 它不一定總是在工作。版本、網路模式、服務啟動順序都可能讓它失效,而失效的時候沒有任何錯誤訊息,就只是連不上
- WSL2 的 IP 每次重開機都會變,所以「繞過 relay、直接打 IP」這個 workaround 撐不過下一次重開機
# 治標:手動指定 WSL2 當下的 IP
ssh -L 8000:172.x.x.x:8000 使用者@遠端主機
# ^^^^^^^^^^ 重開機就要重查一次能動,但每次重開機都要重來一遍。
四、正解:讓服務自己上 VPN
與其層層轉發,不如讓跑服務的那台機器自己有一個穩定位址。把 Tailscale(或其他 WireGuard 系的 mesh VPN)直接裝進跑服務的那一層——以上面的例子來說,就是裝進 WSL2 裡面,而不是只裝在 Windows 上。
前後對比:
之前: 本機 ─ssh -L─→ Windows ─localhost relay─→ WSL2 服務
↑ 這層會壞、IP 會變
之後: 本機 ─────Tailscale 加密────→ WSL2 服務
差別在哪:
| 維度 | SSH tunnel | 服務層直接上 VPN |
|---|---|---|
| 連線前置動作 | 每次都要先建 tunnel | 不用,直接開瀏覽器 |
| 中間層 | 必須維持 SSH 連線開著 | 不需要中轉 |
| IP 變動 | 重開機要重查、改指令 | hostname 永久穩定 |
| 訪問方式 | http://localhost:8000 | http://<主機名>:8000(Magic DNS) |
| 多裝置 | 每台裝置各建一條 tunnel | 手機、平板、筆電全部直接通 |
| 認證 | SSH 金鑰 | VPN 帳號(WireGuard 加密) |
兩個沒預期到的好處
- 跟特定服務解耦。之後在同一台機器上再跑別的東西(Home Assistant、Jupyter、任何 dev server),全部自動可從所有裝置連到,不用每個服務各建一條隧道
- 「永遠連著」的體感。用起來就像那台機器跟你在同一個區網,不管你人在哪裡
代價
| 項目 | 麻煩程度 |
|---|---|
| 裝 VPN client | 一行安裝指令,兩分鐘 |
| 跑 daemon | 需要 systemd。WSL2 預設沒開,在 /etc/wsl.conf 加 [boot] systemd=true 即可 |
| 沒有 systemd 的環境 | 改用 userspace 模式(tailscale up --tun=userspace-networking),自用完全夠 |
五、什麼時候還是用 SSH tunnel 就好
不是每個情境都值得裝 VPN:
- 一次性的、臨時的:偶爾連一次,
ssh -L打完就關,不用為它裝東西 - 對方機器你不能亂裝軟體:公司主機、別人的伺服器
- 本來就只從一台裝置連:VPN 的多裝置優勢用不到
反過來說,只要你發現自己每次開機都在重建同一條 tunnel,那就是該換方案的訊號了。
相關
- 自架服務要開給誰用 —— 想讓「別人」也連得到的時候
- GitHub SSH 設定