你想 SSH 连上一台无法接受入站连接的机器:路由器后面的家用服务器、办公室里的一台树莓派、挂在移动网络或 5G 上的一台机器。老办法——在路由器上转发 22 端口——越来越用不上了,而就算能用,也不是个好主意。本文梳理四种真正可行的方案、各自的代价,以及每种方案的实操示例。它们都不会在你要连的那台机器上打开入站端口。
为什么如今不能再直接转发端口了
有两件事挡在路上。第一,运营商级 NAT(CGNAT):如今大多数家用光纤、有线、移动和 Starlink 连接,都把一个公网 IP 分给许多用户共用,所以“你的”地址上根本没有端口可转发——你能控制的那台路由器,并不是持有公网 IP 的那一台。第二,就算你有一个真正的公网 IP,一个转发出去的 SSH 端口也会在几分钟内被扫描器发现;从它上线那一刻起,日志里就会塞满暴力破解的尝试。下面每种方案的解法形状都一样:那台私有机器主动向某个可达的目标发起一条出站连接,你再顺着这条连接反向进来。出站连接即便穿过 CGNAT 和严密封锁的防火墙也能工作,因为它不过是又一条离开网络的连接而已。
四种方案一览
| 方案 | 你要运行什么 | 额外基础设施 | 机器上是否留有长期密钥? | 最适合 |
|---|---|---|---|---|
| 反向 SSH 隧道 | 机器上跑 ssh -R + autossh | 一台有公网 IP 的 VPS | 是(连 VPS 用) | 你已经租了 VPS |
| 网状 VPN(Tailscale / WireGuard) | 两端各装一个 VPN 客户端 | 一个协调服务 | 你平常的 SSH 密钥 | 要连好几台机器 |
| Cloudflare Tunnel | 机器上跑 cloudflared | 一个 Cloudflare 账户 + 域名 | 你平常的 SSH 密钥 | 你已经在用 Cloudflare |
| 出站 Connector | 机器上跑一个小代理程序 | 一个 Termalin 账户 | 否——用短期证书 | 浏览器访问,不想要 VPS 或 VPN |
前三种方案都让你能从笔记本上照常执行 ssh you@…。最后一种则改为从浏览器或 AI 代理去访问那台机器。接着往下看哪种适合你。
方案一——借道 VPS 的反向 SSH 隧道
如果你已经租了一台便宜的、带公网 IP 的 VPS,那台私有机器就能拨出到它,并把自己的 SSH 端口转发回来。在 NAT 之后的机器上:
ssh -N -R 2222:localhost:22 tunnel@vps.example.com
这会在 VPS 上打开 2222 端口,并把对它的每个连接都引回家用机器的 22 端口。从任何地方,借道 VPS 落到那台机器上:
ssh -J tunnel@vps.example.com -p 2222 you@localhost
一条裸 ssh -R 在网络第一次打嗝时就会断掉,所以用 autossh 把它包起来跑在 systemd 单元里,让它扛过重启和掉线,并加上 -o ServerAliveInterval=30 -o ExitOnForwardFailure=yes。完整原理——包括 GatewayPorts 的坑,以及隧道为何默认绑定到 VPS 的回环地址——都在 SSH 反向隧道详解里。取舍:你得维护一台 VPS,还要在机器上放一把能连到它的长期密钥,而这台 VPS 现在也成了你攻击面的一部分。
方案二——网状 VPN(Tailscale 或 WireGuard)
与其暴露某一个端口,不如把两台机器都放进一个只有它们能看见的私有网络。Tailscale 是最省事的那种做法。在 NAT 之后的机器上:
curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale up
在你的笔记本上也装好 Tailscale,两边都会拿到一个稳定的 100.x.y.z 地址(以及一个 MagicDNS 名称)。然后就只需要:
ssh you@home-box
就像你们在同一个局域网里一样——那台机器根本不会暴露到公网。sudo tailscale up --ssh 甚至能替你搞定 SSH 认证,这样你连密钥都不用管。WireGuard 是同一个思路的手工版:协调工作由你自己运行,对端也要手动配置——工作量更大,但没有第三方协调方。对于一个有 NAS 和几台树莓派的家庭实验室,网状 VPN 通常就是全部答案,也是我在家庭 NAS/服务器指南里默认会用的方案。取舍:每台需要访问的设备都得加入这个网络,也就是要装一个客户端、要有一个账户来运行它。
方案三——Cloudflare Tunnel
如果你的域名已经托管在 Cloudflare 上,cloudflared 能给你一条不需要开放端口的出站隧道。在机器上:
cloudflared tunnel login
cloudflared tunnel create home
在隧道配置里把一个主机名路由到这条隧道,并让它指向本地的 sshd(ssh://localhost:22),然后把 cloudflared 作为服务运行。在客户端一侧,SSH 通过你 ~/.ssh/config(英文)里的 ProxyCommand 连到它:
Host ssh.example.com
ProxyCommand cloudflared access ssh --hostname %h
现在 ssh you@ssh.example.com 会经由 Cloudflare 的边缘节点流向那台机器。免费套餐就够用。取舍:你把自己的 SSH 路由经过了一个第三方,并且依赖上了一个 Cloudflare 账户和一个托管在那里的域名——如果你本来就这么用,很棒;如果不是,就有点杀鸡用牛刀了。
方案四——一个出站 Connector(不要 VPS,不要 VPN)
上面三种方案各自都要求一套常驻的基础设施——一台 VPS、一个 VPN,或一个 Cloudflare 域名。Termalin 的 Connector 把这一切收拢成一条命令,并改用你的浏览器、Termalin 应用或 AI 代理来访问那台机器,而不是本地的 ssh。在那台私有服务器上,一行命令就能装好一个小型静态代理程序(Linux x86_64 和 arm64):
curl -fsSL https://termal.in/tunnel-install.sh | sudo TERMALIN_TOKEN=<token> sh
这个代理通过一条 WebSocket 向外拨到 Termalin 并保持连接;它从不监听任何端口。当你在网页客户端里打开一个终端时,请求会顺着这条链路下来,代理再在 localhost 上连到机器自己的 sshd——它自始至终只搬运不透明的 SSH 字节,所以永远看不到你的凭据或会话明文。接入过程会让机器的 sshd 信任 Termalin 的 SSH 证书颁发机构,于是登录用的是一张短期证书,而不是一把长期待在服务器上的私钥。这就是表格里「机器上是否留有长期密钥」一栏写着“否”的那一行。
坦白说清楚它的代价:你是通过 Termalin 的托管侧去访问那台机器——网页终端、Termalin 桌面与 iOS 应用(它们走的是同一个网关),或者通过 MCP 服务器接入的 AI 代理——而不是从你自己机器上敲一条裸 ssh 命令,而且 Connector 属于付费套餐(或试用)。如果你要的是从笔记本上开一个普通终端,方案一到三更合适。如果你要的是从任意设备上进行浏览器访问——或者让一个代理去操作一台没有公网地址、也没有密钥可偷的机器——那这就是为此打造的方案。同一条仅出站的 Connector 还能为没有开放端口的机器承载轻量级监控与告警。
你该选哪一种?
- **你已经租了一台 VPS:**反向隧道正好把它用起来——一条跑在 autossh 下的
ssh -R就搞定了。 - **你要连的机器不止一台,或者希望它们用起来像一个局域网:**像 Tailscale 这样的网状 VPN 扩展性最好,而且什么都不暴露。
- **你的域名托管在 Cloudflare 上:**它家的隧道是最自然的选择,不用再引入新的服务商。
- **你想要零常驻基础设施、浏览器访问,或受监督的代理访问:**选一个出站 Connector,服务器上不留任何长期密钥。
这四种方案都遵循同一个值得记住的原则:绝不要在一台你承受不起被扫描的机器上打开入站端口。让它主动向外连接——正是这一次方向上的反转,让 NAT 之后的 SSH 变得安全。
Termalin 是一款免费、跨平台的 SSH 客户端,支持保存隧道、提供网页终端,以及面向私有服务器的无密钥 Connector——立即下载,或了解它如何安全地处理你的密钥。