4、SSH隧道与端口转发:本地转发、远程转发、动态转发实战

SSH 隧道,说白了就是把不加密的流量,塞进一条已经加密的 SSH 连接里传输。你想想看,公司内网的数据库、家里的 NAS、或者某个只监听 127.0.0.1 的管理后台,怎么从外面安全访问?SSH 端口转发就是干这个的。

我最早接触这玩意儿,是帮客户排查一个跨机房数据同步的问题。两边防火墙只开了 22 端口,业务端口全封了。当时我就在想,能不能借 SSH 的通道把流量“偷渡”过去?试了一下,还真行。从那以后,SSH 隧道就成了我工具箱里常备的一把螺丝刀。

这一章,咱们把三种转发模式掰开揉碎:本地转发(-L)、远程转发(-R)、动态转发(-D)。每种我都会给实战场景,也会说说我踩过的坑。

核心一句话:SSH 隧道 = 加密的管道 + 端口映射。你只需要一个能 SSH 登录的跳板机,就能把任意 TCP 流量安全地引到目标。

4.1 本地转发(-L):把远程服务拉到本地

本地转发是最常用的一种。它的逻辑是:你在本地监听一个端口,所有发往这个端口的数据,都通过 SSH 加密通道,转发到远程的某个目标地址和端口。

命令格式:

ssh -L [本地地址:]本地端口:目标地址:目标端口 用户@跳板机

实战场景: 公司的 MySQL 数据库只监听内网 3306,你本地开发机连不上。但你有台能 SSH 登录的跳板机(比如堡垒机),它和数据库在同一内网。

# 在本地执行
ssh -L 3307:内网数据库IP:3306 用户@跳板机IP -N

# -N 表示不执行远程命令,只建立隧道,我习惯加上它

之后,你本地连接 127.0.0.1:3307,就等于连上了内网的 MySQL:3306。数据全程加密穿过公网,跳板机只做转发,不存数据。

小技巧: 如果跳板机本身也在目标机器上,可以写成 ssh -L 8080:localhost:80 用户@跳板机,把跳板机自己的 Web 服务映射到本地。

我曾经踩过的坑: 本地转发默认只绑定 127.0.0.1。如果你想让局域网其他机器也能用这个隧道,得加上 -g 参数,或者显式指定 0.0.0.0。比如:

ssh -L 0.0.0.0:3307:内网数据库IP:3306 用户@跳板机IP -g -N

不加 -g,其他机器连你本地的 3307 会直接拒绝。嗯,这个细节我当年调试了半小时才反应过来。

4.2 远程转发(-R):把本地服务暴露到远端

远程转发和本地转发正好相反。你在本地跑着一个服务(比如开发中的 Web 应用),想让远程的同事或跳板机能访问到。你通过 SSH 告诉跳板机:“你监听一个端口,把流量通过隧道传回给我。”

命令格式:

ssh -R [远程地址:]远程端口:本地地址:本地端口 用户@跳板机

实战场景: 你在本地开发机上起了个 Flask 应用,监听 5000 端口。客户在远程,想看效果。但你的开发机没有公网 IP。

# 在本地执行
ssh -R 9000:localhost:5000 用户@公网跳板机IP -N

之后,客户访问 跳板机IP:9000,流量就会通过 SSH 隧道,直接打到你家电脑的 5000 端口上。

注意: 远程转发有个常见限制——跳板机的 sshd 配置里 GatewayPorts 默认是 no,这意味着远程端口只绑定在跳板机的 127.0.0.1 上,外部无法访问。你需要让运维把 /etc/ssh/sshd_config 里的 GatewayPorts yes 打开,或者你自己用 -R 0.0.0.0:9000:localhost:5000 来请求绑定所有接口。

我记得有一次帮朋友调试内网穿透,他死活连不上远程转发的端口。我让他检查 sshd_config,果然 GatewayPorts 没开。改完重启 sshd,立马通了。

4.3 动态转发(-D):搭建一个迷你 SOCKS5 代理

动态转发是三种模式里最灵活的。它不指定固定的目标地址和端口,而是在本地启动一个 SOCKS5 代理。你只要把浏览器或应用的代理设置指向这个端口,所有流量都会通过 SSH 隧道,由跳板机代为发出。

命令格式:

ssh -D [本地地址:]本地端口 用户@跳板机 -N

实战场景: 你在咖啡厅连了公共 WiFi,不想让 HTTP 流量裸奔。或者你想让浏览器“假装”从公司网络访问某个网站。

# 在本地执行
ssh -D 1080 用户@跳板机IP -N

# 然后设置浏览器 SOCKS5 代理为 127.0.0.1:1080

之后,浏览器发起的每个 TCP 连接,都会通过 SSH 隧道,从跳板机出去。目标服务器看到的是跳板机的 IP,不是你咖啡厅的 IP。

进阶用法: 配合 proxychains,可以让任何命令行工具走动态转发隧道。比如 proxychains4 curl https://api.example.com。我经常用这招来调试一些只允许特定 IP 访问的 API。

4.4 三种转发对比与选型

为了让你一眼看明白,我整理了一张表:

转发类型 命令参数 核心逻辑 典型场景 谁发起连接
本地转发 -L 本地监听 → 隧道 → 远端目标 访问内网数据库、管理后台 本地客户端
远程转发 -R 远端监听 → 隧道 → 本地服务 内网穿透、暴露本地开发环境 远端客户端
动态转发 -D 本地 SOCKS5 代理 → 隧道 → 任意目标 全局代理、加密上网、绕过限制 本地客户端(动态决定)

怎么选? 简单粗暴:

  • 你想访问远程的某个固定服务 → 本地转发
  • 你想让远程访问你本地的某个服务 → 远程转发
  • 你想让所有流量都走隧道,或者目标地址不固定 → 动态转发

4.5 隧道持久化与保活

SSH 隧道默认不太稳定。网络抖动、超时、或者 SSH 会话被服务器断开,隧道就断了。你不可能每次都手动重连吧?

我一般用 autossh 来解决。它会自动检测隧道状态,断了就重连。

# 安装 autossh(macOS: brew install autossh, Ubuntu: apt install autossh)
autossh -M 0 -o "ServerAliveInterval 30" -o "ServerAliveCountMax 3" \
  -L 3307:内网数据库IP:3306 用户@跳板机IP -N

-M 0 表示禁用 autossh 自己的监控端口(新版推荐)。ServerAliveInterval 30 让 SSH 每 30 秒发一个心跳包,防止被防火墙掐断。

生产环境建议: 把 autossh 配置成 systemd 服务,开机自启。这样隧道就像网络服务一样稳定。我曾经用这个方案,一条隧道跑了 200 多天没断过。

4.6 安全加固:别让隧道变成后门

SSH 隧道虽然加密,但配置不当就是个大漏洞。我见过有人把远程转发端口暴露到公网,结果被扫描器发现,成了别人攻击内网的跳板。

几条硬规矩:

  • 限制转发来源: 能用 127.0.0.1 就别用 0.0.0.0。只有需要外部访问时才放开。
  • 使用专用用户: 创建一个只能做端口转发的 SSH 用户,禁止 shell 登录。在 ~/.ssh/authorized_keys 里加上 command="/usr/sbin/nologin" 或 restrict 选项。
  • 禁用 TCP 转发(如果不需要): 在 sshd_config 里设置 AllowTcpForwarding no,或者针对特定用户设置 permitopen="目标IP:端口" 白名单。

我曾经犯过的错: 有一次为了图方便,在跳板机上开了 GatewayPorts yes,还把远程转发端口设成了 0.0.0.0。结果第二天发现日志里有大量来自国外的扫描记录。还好只是测试环境。从那以后,我坚持最小权限原则——能绑 127.0.0.1 绝不绑 0.0.0.0。

4.7 一张图看懂三种转发

下面这张 SVG 图,把三种转发的数据流向画清楚了。你可以对照着看,理解起来会更直观。

SSH 三种端口转发模式对比 SSH 端口转发三种模式 SSH 加密隧道 明文流量 目标服务 本地转发 (-L) 本地客户端 监听 :3307 SSH 隧道 MySQL:3306 本地连 127.0.0.1:3307 = 连远程 MySQL:3306 远程转发 (-R) 本地 Web:5000 SSH 隧道 监听 :9000 远程访问跳板机:9000 = 连本地 Web:5000 动态转发 (-D) 浏览器 SOCKS5 :1080 SSH 隧道 任意目标 浏览器设代理 :1080 所有流量走隧道

4.8 本章小结

SSH 端口转发,说白了就是“借道”。你有一台能 SSH 的机器,就能用它做跳板,把各种 TCP 服务安全地连起来。

  • 本地转发(-L):把远程服务拉到本地,适合访问内网资源。
  • 远程转发(-R):把本地服务暴露到远端,适合内网穿透。
  • 动态转发(-D):搭一个 SOCKS5 代理,适合全局加密或绕过限制。

记住:隧道虽好,安全第一。端口别乱开,权限别给大。用 autossh 保活,用 systemd 托管,一条隧道跑一年不是问题。