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 图,把三种转发的数据流向画清楚了。你可以对照着看,理解起来会更直观。
4.8 本章小结
SSH 端口转发,说白了就是“借道”。你有一台能 SSH 的机器,就能用它做跳板,把各种 TCP 服务安全地连起来。
- 本地转发(-L):把远程服务拉到本地,适合访问内网资源。
- 远程转发(-R):把本地服务暴露到远端,适合内网穿透。
- 动态转发(-D):搭一个 SOCKS5 代理,适合全局加密或绕过限制。
记住:隧道虽好,安全第一。端口别乱开,权限别给大。用 autossh 保活,用 systemd 托管,一条隧道跑一年不是问题。