家裡的 Linux 主機可以執行 Cockpit、開發環境、監控面板和檔案服務,但家庭寬頻往往沒有公網 IPv4。所謂“內網穿透”,本質上是為外部裝置建立一條能夠到達內網服務的路徑。
這篇文章介紹四種常見方案:
- 直接使用運營商分配的公網 IPv6;
- 通過有公網 IP 的 VPS 建立 SSH 反向隧道;
- 使用 Cloudflare Tunnel 釋出 Web 或 SSH 服務;
- 使用 Tailscale 組建只對授權裝置開放的私有網路。
**安全原則:**遠端訪問不等於把服務暴露給整個網際網路。SSH、Cockpit、資料庫和管理面板應優先使用私有網路或身份認證;只有確實需要公開訪問的 Web 服務,才應設定公開域名。
1. 先選擇合適的方案
| 方案 | 額外條件 | 最適合 | 是否公開服務埠 | 主要限制 |
|---|---|---|---|---|
| 公網 IPv6 | 運營商提供可入站的 IPv6 | 自己管理的 SSH、HTTPS 服務 | 是 | 地址可能變化,路由器和主機防火牆必須正確設定 |
| SSH 反向隧道 | 一臺有公網 IP 的 VPS | 臨時轉發、完全自控的運維通道 | VPS 至少開放 SSH | 需要維護 VPS、金鑰和長連線 |
| Cloudflare Tunnel | Cloudflare 賬戶;公開 hostname 還需要託管域名 | 對外發布 Web 應用,或通過 Access 訪問 SSH | 否 | SSH 等非 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:2022;MaxSessions 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 立即退出;ServerAliveInterval和ServerAliveCountMax用於發現失效連線。
登入 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 同時開放到公網。

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 會要求使用者定期重新完成身份驗證。策略語法和功能會持續更新,應同時參考 Grants 與 Tailscale 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 -t、journalctl -u sshd 和 ss -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 和隧道。
無論選擇哪一種,都應堅持最小暴露面、金鑰或身份認證、最小權限和可審計日誌。先建立私有訪問,再決定哪些服務確實需要公開,通常比“先開埠再加固”更安全。