家裡的 Linux 主機可以執行 Cockpit、開發環境、監控面板和檔案服務,但家庭寬頻往往沒有公網 IPv4。所謂“內網穿透”,本質上是為外部裝置建立一條能夠到達內網服務的路徑。

這篇文章介紹四種常見方案:

  1. 直接使用運營商分配的公網 IPv6;
  2. 通過有公網 IP 的 VPS 建立 SSH 反向隧道;
  3. 使用 Cloudflare Tunnel 釋出 Web 或 SSH 服務;
  4. 使用 Tailscale 組建只對授權裝置開放的私有網路。

**安全原則:**遠端訪問不等於把服務暴露給整個網際網路。SSH、Cockpit、資料庫和管理面板應優先使用私有網路或身份認證;只有確實需要公開訪問的 Web 服務,才應設定公開域名。

1. 先選擇合適的方案

方案額外條件最適合是否公開服務埠主要限制
公網 IPv6運營商提供可入站的 IPv6自己管理的 SSH、HTTPS 服務地址可能變化,路由器和主機防火牆必須正確設定
SSH 反向隧道一臺有公網 IP 的 VPS臨時轉發、完全自控的運維通道VPS 至少開放 SSH需要維護 VPS、金鑰和長連線
Cloudflare TunnelCloudflare 賬戶;公開 hostname 還需要託管域名對外發布 Web 應用,或通過 Access 訪問 SSHSSH 等非 HTTP 服務需要用戶端 cloudflared 或 Cloudflare One Client
Tailscale訪問端和目標端安裝用戶端個人裝置、團隊運維、整個家庭區域網訪問裝置需要加入同一 tailnet;依賴 Tailscale 控制平面
flowchart LR
  A[需要从外部访问内网] --> B{访问者是否都能安装客户端}
  B -->|可以| C[Tailscale]
  B -->|不可以| D{是否为公开 Web 应用}
  D -->|| E[Cloudflare Tunnel + Access/WAF]
  D -->|| F{有可入站的公网 IPv6}
  F -->|| G[IPv6 + 防火墙 + SSH/HTTPS]
  F -->|没有| H[SSH 反向隧道 + VPS]

對個人和家庭環境,我通常優先選擇 Tailscale:不需要域名、VPS 或路由器埠對映。需要讓普通訪客訪問 Web 應用時,再選擇 Cloudflare Tunnel。公網 IPv6 和 SSH 反向隧道則適合希望自己控制網路路徑的人。

2. 使用公網 IPv6 直接訪問

公網 IPv4 稀缺並不代表家庭網路一定沒有公網地址。很多運營商會下發全球單播 IPv6 地址;只要入站流量沒有被運營商或路由器攔截,就可以直接訪問家中的主機。

2.1 確認主機獲得了全球 IPv6 地址

在內網主機上檢查地址和預設路由:

ip -6 address show scope global
ip -6 route show default

fe80::/10 是鏈路本地地址,只能在同一二層網路使用;fc00::/7 屬於唯一本地地址,也不能從公網直接訪問。這裡需要的是具有公網路由能力的全球單播地址。各類地址的定義可以查閱 RFC 4291

接著確認主機可以主動訪問 IPv6 網路:

ping -6 -c 4 2606:4700:4700::1111

主動訪問成功仍不代表公網可以入站。還需要檢查光貓或路由器的 IPv6 防火牆。IPv6 通常不需要 IPv4 風格的 NAT 埠轉發,但家用路由器常會預設拒絕所有入站連線。

2.2 設定 SSH 和防火牆

先確認 SSH 正在監聽 IPv6:

sudo ss -lntp | grep ':22'

輸出中出現 [::]:22,表示 sshd 正在 IPv6 地址上監聽。使用 firewalld 的系統可以放行 SSH 服務:

sudo firewall-cmd --permanent --add-service=ssh
sudo firewall-cmd --reload
sudo firewall-cmd --list-services

還要在路由器的 IPv6 入站規則中放行 TCP 22。能限制來源 IPv6 字首時,應只允許自己的辦公網路或移動裝置地址,而不是向所有來源開放。

在關閉密碼登入之前,先設定並驗證公鑰:

ssh-keygen -t ed25519 -a 100
ssh-copy-id [email protected]

確認新終端能夠用金鑰登入後,再建立 /etc/ssh/sshd_config.d/50-hardening.conf

PermitRootLogin no
PubkeyAuthentication yes
PasswordAuthentication no

檢查設定後平滑重新載入 sshd,不要直接中斷當前會話:

sudo sshd -t
sudo systemctl reload sshd

2.3 設定 AAAA 記錄並測試

如果 IPv6 地址穩定,可以為域名新增一條 DNS AAAA 記錄:

home.example.com.  AAAA  2001:db8:1234:5678::10

這裡的 2001:db8::/32 是說明文件示例地址,必須替換為自己的真實地址。若用 Cloudflare 管理 DNS,直接連線 SSH 的 AAAA 記錄應設為 DNS only;普通 Cloudflare 代理並不會把原始 SSH 連線轉發到主機。

然後在一條真正的外部 IPv6 網路上測試,而不是在家中 Wi-Fi 內測試:

ssh -6 [email protected]

如果運營商會定期更換 IPv6 字首,需要設定動態 DNS。地址或字首頻繁變化時,Tailscale 或 Cloudflare Tunnel 通常更省心。

3. 使用 SSH 反向隧道

SSH 遠端埠轉發會從內網主機主動連線 VPS,再讓 VPS 上的監聽埠回到內網服務。整個過程只要求內網主機能夠訪問網際網路,不要求家庭網路擁有公網地址。

flowchart LR
  C[外部电脑] -->|SSH ProxyJump| V[VPS 公网 22]
  V -->|127.0.0.1:2022| T[SSH 反向隧道]
  T -->|127.0.0.1:22| H[家中 Linux 主机]
  H -->|主动建立连接| V

下面以“把家中主機的 22 埠轉發到 VPS 的 127.0.0.1:2022”為例。監聽地址刻意使用迴環地址,避免把轉發埠直接暴露到公網。

3.1 準備 VPS

任何能夠穩定執行 OpenSSH、擁有公網 IP 的 VPS 都可以。先完成系統更新、普通管理員賬戶和防火牆設定;更完整的基礎加固可以參考雲伺服器安全設定

以 RHEL、Fedora 或相容發行版為例:

sudo dnf install -y openssh-server
sudo systemctl enable --now sshd
sudo firewall-cmd --permanent --add-service=ssh
sudo firewall-cmd --reload

雲廠商若還有安全組,只需要先放行 VPS 的 SSH 埠。不需要為 2022 新增公網規則,因為它只在 VPS 的迴環地址上監聽。

3.2 建立專用隧道使用者

不要讓隧道長期使用 VPS 的 root 或日常管理員賬戶:

sudo useradd --create-home tunnel
sudo passwd tunnel

在家中主機上生成一把專用 Ed25519 金鑰,並複製到 VPS:

ssh-keygen -t ed25519 -a 100 -f ~/.ssh/id_ed25519_tunnel
ssh-copy-id -i ~/.ssh/id_ed25519_tunnel.pub [email protected]

首次連線時應通過 VPS 控制台或雲廠商提供的資訊人工核對主機指紋。確認專用金鑰可以認證後,立即在 VPS 上鎖定 tunnel 使用者的密碼;鎖定密碼不會影響公鑰認證:

ssh -NT -i ~/.ssh/id_ed25519_tunnel [email protected]
# 在另一个 VPS 管理会话中执行:
sudo passwd --lock tunnel

第一條測試命令會保持連線,確認沒有認證錯誤後按 Ctrl+C 退出。

3.3 限制遠端轉發權限

在 VPS 上建立 /etc/ssh/sshd_config.d/60-reverse-tunnel.conf

Match User tunnel
    PasswordAuthentication no
    AllowAgentForwarding no
    AllowTcpForwarding remote
    GatewayPorts no
    PermitListen 127.0.0.1:2022
    PermitTTY no
    MaxSessions 0

Match all

這些設定只允許 tunnel 使用者建立遠端轉發,並且只能監聽 127.0.0.1:2022MaxSessions 0 禁止 shell、命令和 SFTP 會話,但仍允許埠轉發。與直接設定 GatewayPorts yes 相比,這樣不會意外把任意轉發埠暴露到 VPS 的公網網路卡。各指令的準確含義可查閱 OpenSSH 的 sshd_config 手冊

應用前必須先檢查語法:

sudo sshd -t
sudo systemctl reload sshd

3.4 建立並驗證隧道

在家中主機執行:

ssh -NT \
  -o ExitOnForwardFailure=yes \
  -o ServerAliveInterval=30 \
  -o ServerAliveCountMax=3 \
  -i ~/.ssh/id_ed25519_tunnel \
  -R 127.0.0.1:2022:127.0.0.1:22 \
  [email protected]
  • -N 表示不執行遠端命令;
  • -T 表示不分配偽終端;
  • -R 建立遠端埠轉發;
  • ExitOnForwardFailure 確保監聽失敗時 SSH 立即退出;
  • ServerAliveIntervalServerAliveCountMax 用於發現失效連線。

登入 VPS 後驗證監聽地址:

ss -lnt | grep 2022
ssh -p 2022 [email protected]

外部電腦可以把 VPS 作為跳板,VPS 管理賬戶與隧道賬戶應分開:

ssh -J [email protected] -p 2022 [email protected]

3.5 使用 systemd 自動重連

OpenSSH 的存活檢測配合 systemd 重啟策略已經能可靠維護隧道,不一定需要額外安裝 autossh。

在家中主機建立 /etc/systemd/system/ssh-reverse-tunnel.service。必須把使用者名稱、主目錄和域名替換為實際值;systemd 的 ExecStart 不會展開 ~

[Unit]
Description=Reverse SSH tunnel to VPS
Wants=network-online.target
After=network-online.target

[Service]
Type=simple
User=home_user
ExecStart=/usr/bin/ssh -NT \
  -o BatchMode=yes \
  -o ExitOnForwardFailure=yes \
  -o ServerAliveInterval=30 \
  -o ServerAliveCountMax=3 \
  -i /home/home_user/.ssh/id_ed25519_tunnel \
  -R 127.0.0.1:2022:127.0.0.1:22 \
  [email protected]
Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target

啟用並檢視日誌:

sudo systemctl daemon-reload
sudo systemctl enable --now ssh-reverse-tunnel.service
systemctl status ssh-reverse-tunnel.service
journalctl -u ssh-reverse-tunnel.service -f

如果確實要讓 VPS 的 2022 直接監聽公網,才將 GatewayPorts 改為 clientspecified,並把 -R 的監聽地址改為 0.0.0.0::。同時必須用 VPS 防火牆或安全組限制來源地址。對管理用 SSH,優先保留迴環監聽和 ProxyJump。

4. 使用 Cloudflare Tunnel

Cloudflare Tunnel 在內網執行 cloudflared,由它主動建立到 Cloudflare 的出站連線。因此家中不需要公網 IP,也不需要在路由器上開放入站埠。

flowchart LR
  U[访问者] -->|HTTPS / Access| C[Cloudflare 网络]
  C -->|已建立的 Tunnel| D[cloudflared]
  D --> W[内网 Web 服务]

公開 Web 應用和 SSH 的工作方式不同:

  • HTTP/HTTPS 可以釋出為普通 hostname;
  • SSH、RDP 和任意 TCP 服務需要訪問端安裝 cloudflared,或通過 Cloudflare One Client 訪問私有網段;
  • Cloudflare Access 可以在請求到達源站前增加身份驗證和訪問策略。

4.1 建立 Tunnel

公開 hostname 需要一個已接入 Cloudflare 的域名。進入 Cloudflare 控制台的 Networking → Tunnels,建立一個 cloudflared Tunnel。控制台名稱可能隨版本調整,以官方 Tunnel 設定說明文件為準。

建立完成後,控制台會生成一條包含 Tunnel token 的安裝命令。token 等同於該 Tunnel 聯結器的憑據,不要放進文章、Git 倉庫、截圖或容器映像;一旦洩漏,應立即輪換。

4.2 用 Docker Compose 執行 cloudflared

建立 .env

TUNNEL_TOKEN=替换为控制台生成的真实_token

限制檔案權限,並確保它已加入 .gitignore

chmod 600 .env

建立 compose.yaml

services:
  cloudflared:
    image: cloudflare/cloudflared:latest
    restart: unless-stopped
    command: tunnel --no-autoupdate run --token ${TUNNEL_TOKEN}
    extra_hosts:
      - "host.docker.internal:host-gateway"

啟動並檢視日誌:

docker compose up -d
docker compose logs -f cloudflared

容器內的 localhost 指向容器自身,而不是 Docker 主機。源服務位於主機時,可以使用 host.docker.internal 和上面的 host-gateway 對映;源服務也是容器時,最好讓兩個服務加入同一 Docker 網路並使用 Compose 服務名,例如 http://grafana:3000

4.3 釋出 Web 應用

開啟剛建立的 Tunnel,在 Routes 中新增 Published application

Hostname: cockpit.example.com
Service URL: https://host.docker.internal:9090

如果 cloudflared 直接安裝在主機而不是容器中,可以使用 https://localhost:9090。源站使用自簽名憑證時,優先讓 cloudflared 信任正確的 CA;僅在清楚風險時,才在 Origin settings 中啟用 No TLS Verify

Tunnel 只解決網路可達性。Cockpit、管理面板和內部工具還應建立 Cloudflare Access 自託管應用,並設定允許的身份、郵箱域或使用者組。Access 是源站認證之前的額外防線,不能替代源站自己的賬戶和權限控制。

4.4 通過 Cloudflare Access 連線 SSH

在 Tunnel 的 Routes 中新增 Published application:

Hostname: ssh.example.com
Service: SSH
Service URL: localhost:22

再為 ssh.example.com 建立 Cloudflare Access 自託管應用和允許策略。訪問端也要安裝 cloudflared,並在 ~/.ssh/config 中加入:

Host ssh.example.com
    ProxyCommand /usr/local/bin/cloudflared access ssh --hostname %h

macOS Homebrew 等安裝方式的實際路徑可能不同,可以用 command -v cloudflared 查詢。連線時瀏覽器會開啟身份認證頁面:

ssh [email protected]

這種方式不是把公網 22 埠直接轉發到家中,而是由用戶端 cloudflared 建立經過 Access 驗證的連線。完整步驟可參考 Cloudflare 的用戶端 cloudflared SSH 指南

如果希望像在區域網中一樣訪問多種非 HTTP 服務,可以向 Tunnel 新增內網 IP/CIDR route,再讓授權裝置通過 Cloudflare One Client 接入。該模式需要裝置註冊、Split Tunnel 和 Gateway 網路策略,詳見私有網路路由說明文件

4.5 示例:安全釋出 Cockpit

在 RHEL、Fedora 或相容發行版安裝 Cockpit:

sudo dnf install -y cockpit
sudo systemctl enable --now cockpit.socket
sudo ss -lntp | grep 9090

Cockpit 預設使用 HTTPS 9090 埠。設定 Tunnel 後,再給 hostname 新增 Cloudflare Access 策略。不要僅因為前面有 Tunnel 就使用弱密碼,也不要把 9090 同時開放到公網。

通過 Cloudflare Tunnel 訪問 Cockpit 登入頁

5. 使用 Tailscale

Tailscale 基於 WireGuard 為裝置建立私有的 tailnet。它會優先嚐試裝置間直連;無法直連時可以通過中繼傳輸。無論哪種路徑,都不需要在家庭路由器上開放 SSH 或 Cockpit 埠。

flowchart LR
  L[笔记本 / 手机] <-->|加密 tailnet| H[家中 Linux 主机]
  L -->|MagicDNS 名称| H
  H --> N[仅内网可见的服务]

5.1 在兩端安裝並加入 tailnet

Linux 可以使用官方安裝指令碼;不希望執行 curl | sh 時,也可以按官方軟體源說明手動安裝:

curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale up
tailscale status
tailscale ip

在筆記本或手機上安裝 Tailscale,使用同一組織的身份登入。啟用 MagicDNS 後,可以直接使用裝置名訪問:

tailscale ping home-server
ssh home_user@home-server

這裡仍然使用系統原有的 OpenSSH 和金鑰認證,只是網路流量經由 tailnet 到達目標主機。也可以訪問其他埠:

https://home-server:9090
http://home-server:3000

5.2 使用統一域名訪問內外網

家庭服務常遇到一個地址割裂問題:在家時使用 10.x.x.x,離開家庭網路後改用 VPN 地址或另一個反向代理域名。更簡單的設計是讓內外網始終使用同一個域名,並讓它解析到服務的內網 IP:

immich.home.example.com -> 10.0.0.20
grafana.home.example.com -> 10.0.0.20

家庭區域網中的裝置直接訪問這個內網地址;外部裝置加入 tailnet 後,則通過 Tailscale subnet router 到達同一個網段。Tailscale 在這裡不是為每項服務提供新的 100.x.x.x 地址,而是充當一條安全的遠端區域網路由。

flowchart LR
  L[家庭局域网设备] --> D[家庭 DNS]
  R[外部设备] -->|Tailscale Split DNS| D
  D -->|统一域名解析| I[内网 IP]
  L -->|局域网直连| I
  R -->|Subnet Router| I
  I --> N[Nginx / 服务]

為了讓外部裝置解析同一組內部域名,可以在 Tailscale 中設定 Split DNS,把 home.example.com 等內部域的查詢交給家庭 DNS。這樣每一層只承擔一種職責:

  • DNS 將統一域名解析到內網 IP;
  • Tailscale 讓外部裝置能夠路由到家庭內網;
  • Nginx 根據域名把請求轉發到對應服務。

用戶端不需要判斷自己當前位於家中、公司還是行動網路,始終訪問 https://immich.home.example.com 即可。Subnet Router、Split DNS 和 HTTPS 憑證可以分別設定,不必為每個服務再建立一套外網地址體系。

5.3 可選:啟用 Tailscale SSH

Tailscale SSH 可以使用 tailnet 身份和策略管理 SSH 認證,不必分發傳統 SSH 公鑰。在伺服器上啟用:

sudo tailscale set --ssh

生產環境不要長期沿用寬泛的預設訪問規則。新的網路訪問規則應使用 grants,Tailscale SSH 身份規則則放在獨立的 ssh 區段。下面是一個限制到非 root SSH 的結構示例,儲存前要替換使用者和標籤,並在管理控制台中驗證策略:

{
  "groups": {
    "group:home-admins": ["[email protected]"]
  },
  "tagOwners": {
    "tag:home-server": ["group:home-admins"]
  },
  "grants": [
    {
      "src": ["group:home-admins"],
      "dst": ["tag:home-server"],
      "ip": ["tcp:22", "tcp:9090"]
    }
  ],
  "ssh": [
    {
      "action": "check",
      "src": ["group:home-admins"],
      "dst": ["tag:home-server"],
      "users": ["autogroup:nonroot"],
      "checkPeriod": "12h"
    }
  ]
}

check 會要求使用者定期重新完成身份驗證。策略語法和功能會持續更新,應同時參考 GrantsTailscale SSH 官方說明文件。

還需要在 Machines 頁面或註冊伺服器時給目標裝置分配 tag:home-server;否則上面的目標選擇器不會匹配該伺服器。

5.4 訪問不能安裝 Tailscale 的內網裝置

印表機、NAS、攝像頭等裝置可能無法安裝用戶端。這時可以讓一臺常開的 Linux 主機充當 subnet router。

先啟用轉發:

sudo sh -c 'printf "%s\n" "net.ipv4.ip_forward = 1" "net.ipv6.conf.all.forwarding = 1" > /etc/sysctl.d/99-tailscale.conf'
sudo sysctl -p /etc/sysctl.d/99-tailscale.conf

再發布真實的家庭網段:

sudo tailscale set --advertise-routes=192.168.50.0/24

到 Tailscale 管理控制台的 Machines 頁面批准該 route,並用 grants 限制哪些身份可以訪問它。Linux 用戶端還要顯式接受子網路由:

sudo tailscale set --accept-routes

用戶端所在網路不能與家庭網段重疊;例如兩邊都使用 192.168.1.0/24 時,路由會產生歧義。完整設定與高可用方案見 Subnet routers 說明文件。

6. 常見故障排查

IPv6 能出站但不能入站

依次檢查運營商是否允許入站、光貓/路由器 IPv6 防火牆、主機防火牆、服務是否監聽 [::],以及 DNS AAAA 是否仍指向當前地址。不要用同一家庭 Wi-Fi 作為唯一測試環境。

SSH 反向隧道啟動失敗

在家中主機使用詳細日誌測試:

ssh -vvv -NT -R 127.0.0.1:2022:127.0.0.1:22 [email protected]

在 VPS 檢查 sshd -tjournalctl -u sshdss -lntp。常見原因包括 PermitListen 不匹配、埠已佔用、主機指紋未寫入執行使用者的 known_hosts,或 systemd 單元中錯誤使用了 ~

Cloudflare Tunnel 顯示健康但返回 502

健康只表示 cloudflared 已連線 Cloudflare,不表示它能訪問源服務。在 cloudflared 所在主機或容器中測試 Service URL,重點檢查協定、埠、TLS 憑證和 Docker 網路。容器中的 localhost 幾乎總是最先要排查的地方。

Tailscale 名稱無法解析或連線被拒絕

tailscale status
tailscale ping home-server
tailscale netcheck

名稱問題檢查 MagicDNS;網路拒絕檢查 grants、裝置標籤與目標服務監聽地址;Tailscale SSH 還要檢查獨立的 ssh 策略。subnet router 場景則同時檢查 route 是否已批准,以及 Linux 用戶端是否接受路由。

7. 總結

四種方案解決的是不同問題:

  • Tailscale 最適合授權裝置之間的日常遠端訪問;
  • Cloudflare Tunnel 最適合帶域名、身份驗證和邊緣防護的 Web 應用;
  • 公網 IPv6 路徑最直接,但安全責任也完全落在主機和路由器上;
  • SSH 反向隧道 靈活且可自控,代價是持續維護 VPS 和隧道。

無論選擇哪一種,都應堅持最小暴露面、金鑰或身份認證、最小權限和可審計日誌。先建立私有訪問,再決定哪些服務確實需要公開,通常比“先開埠再加固”更安全。