把 SSH 隧道用明白:本地转发、远程转发与 SOCKS 代理

SSH 隧道连接开发机、跳板机与内网服务

数据库只允许内网访问、本地服务需要临时给远端联调、浏览器要经过跳板机访问测试环境——这些场景不一定要先搭一套 VPN。SSH 端口转发可以借用已有的登录链路建立加密通道,但三个常用参数 -L-R-D 的方向很容易混淆。本文从“监听发生在哪里”出发,把它们一次讲清。

先建立一个判断模型

SSH 隧道至少涉及三方:运行 ssh 的客户端、接受登录的 SSH 服务端,以及最终目标服务。判断命令时先问两个问题:端口在哪台机器监听,连接最终从哪一侧发出?

模式 监听位置 典型用途
-L 本地转发 SSH 客户端 本机访问远端内网服务
-R 远程转发 SSH 服务端 让远端访问客户端侧服务
-D 动态转发 SSH 客户端 建立按请求选目标的 SOCKS 代理

隧道只加密客户端到 SSH 服务端这一段;服务端到目标服务的后半程仍使用目标协议本身,不能把它等同于端到端加密或完整 VPN。

本地转发:把内网端口拉到本机

假设数据库 db.internal:5432 只能被跳板机访问,可在开发机执行:

1
2
3
ssh -N \
-L 127.0.0.1:15432:db.internal:5432 \
ops@bastion.example.com

之后应用连接 127.0.0.1:15432,流量便沿 SSH 通道抵达跳板机,再由跳板机连接数据库。目标主机名 db.internal 也从跳板机所在网络解析。-N 表示不执行远程命令,只做转发。

本地转发经过跳板机访问内网数据库

注意监听地址明确写成 127.0.0.1。若使用 0.0.0.0,同一网络中的其他机器也可能连接这个端口,相当于无意中替内网服务开了入口。

远程转发:把本地服务交给远端访问

本地开发服务监听在 127.0.0.1:3000,需要让跳板机上的同事或程序临时访问时,可以反向建立通道:

1
2
3
ssh -N \
-R 127.0.0.1:18080:127.0.0.1:3000 \
ops@bastion.example.com

此时 18080 监听在跳板机的回环地址,收到的连接经隧道返回开发机,再访问开发机的 3000 端口。这里的两个 127.0.0.1 属于不同机器:前者是远端监听地址,后者是客户端侧目标。若要允许其他主机访问远端监听端口,还会受到服务端 GatewayPorts 和防火墙约束;不要为了省事直接暴露到公网。

动态转发:让应用自己选择目标

当目标不止一个时,-D 会在本机创建 SOCKS 代理:

1
2
ssh -N -D 127.0.0.1:1080 ops@bastion.example.com
curl --proxy socks5h://127.0.0.1:1080 https://service.internal/health

socks5h 中的 h 表示由代理侧解析域名,因此本机不认识的内网域名也能工作。只有显式配置了该代理的应用才会走隧道,其他系统流量不会自动接管。

本地、远程与动态转发的不同网络路径

把临时命令变成可靠配置

频繁使用的隧道适合写进 ~/.ssh/config

1
2
3
4
5
6
7
Host prod-db-tunnel
HostName bastion.example.com
User ops
LocalForward 127.0.0.1:15432 db.internal:5432
ExitOnForwardFailure yes
ServerAliveInterval 30
ServerAliveCountMax 3

之后运行 ssh -N prod-db-tunnel 即可。ExitOnForwardFailure 能在监听端口创建失败时立即退出,但不能证明最终数据库一定可达;保活参数则能让断网后的失效连接及时结束。

服务端还应按账号收紧权限,例如通过 AllowTcpForwardingPermitOpen 限定转发方向和目标,并关闭不需要的代理、X11 与 agent 转发。密钥认证解决“谁能登录”,转发策略解决“登录后能连哪里”,两者缺一不可。

故障定位清单

遇到“隧道连上但业务不通”,按链路逐段检查:

  1. ss -lntp 确认监听端口和地址是否正确,排除端口占用。
  2. ssh -vvv 查看转发请求是否被服务端策略拒绝。
  3. 登录跳板机,用 nc -vz db.internal 5432 验证它到目标的 DNS、路由和防火墙。
  4. 确认应用连接的是隧道端口,而非原始服务端口;动态代理还要确认 DNS 是否经代理解析。

SSH 转发最实用的记忆法不是背参数,而是画出监听端与目标端:端口在本地用 -L,端口在远端用 -R,目标由应用逐次决定用 -D。方向明确后,配置和排障都会简单许多。