SSH隧道详解:从原理到实战,一篇就够了
作者注:本文不讲晦涩的理论,用“点外卖”的类比和可直接运行的命令,帮你彻底理解SSH隧道是什么、怎么用、服务器要不要配。
一、引言:SSH隧道到底是什么?
SSH隧道(SSH Tunneling),也叫端口转发(Port Forwarding),是利用SSH协议建立的一条加密数据传输通道。它的核心作用就一句话:
把本地一个端口,通过SSH加密连接,映射到远程某个服务的端口上。
通俗地说:你坐在家里,通过一台公网服务器做“跳板”,安全地访问公司内网的服务,或者把家里的服务安全地暴露给外网。
它有三个最常用的场景:
- 本地转发(-L):访问被防火墙限制的内网服务(如数据库、内部网站)
- 远程转发(-R):把内网服务暴露到公网(内网穿透)
- 动态转发(-D):创建SOCKS5代理,实现浏览器级别的全局代理
二、理解原理:一个“点外卖”的类比
为了让你不看代码也能懂,我们用“点外卖”来还原SSH隧道的完整过程。
角色对应关系
| 现实角色 | 技术角色 |
|---|---|
| 你(坐在家里) | 客户端(你的Ubuntu电脑) |
| 公司保安(有工号,能进出公司) | SSH服务器(有公网IP,能访问内网) |
| 老王面馆(在公司内部,只送内网) | 目标服务(如内网数据库,只允许内网IP访问) |
| 你家门口的取餐箱 | 本地监听的端口 |
| 你和保安之间的加密电话线 | SSH加密连接 |
完整流程
- 你给公司保安打电话,报上工号和密码,保安确认你的身份——建立SSH连接(身份认证)
- 你在电话里告诉保安:“我在家门口放了个取餐箱(本地端口),你帮我盯着。如果我往里面放纸条,你就按纸条上的内容,去老王面馆帮我买面,然后加密送回来。”——设置端口转发规则
- 你写了一张纸条:“要一碗牛肉面”(相当于发送数据库查询请求),放进家门口的取餐箱(访问本地端口)
- 保安定时来取餐箱查看,看到纸条后,装进一个只有你俩能解的密码箱(SSH加密),通过电话线传到公司
- 保安收到后解密,以他自己的身份去老王面馆买面(SSH服务器代替你访问内网目标)
- 保安把面加密装进密码箱,通过电话线传回给你,你解密后取出来吃(返回查询结果)
三个关键认知
第一,数据没有“穿透”防火墙。 你的请求根本没直接去找面馆,而是保安替你跑了一趟。防火墙只看到保安在公司内部活动,这是合法的。
第二,隧道就是那条加密电话线。 外界听不到内容(加密),也不知道你在让保安干什么(协议混淆),只看到你和保安在通话(SSH连接)。
第三,隧道只负责搬运,不管内容。 无论是MySQL查询、HTTP请求还是远程桌面,只要是TCP协议的数据,都能走SSH隧道。
三、实战配置:三条命令走天下
以下所有命令在Ubuntu客户端上执行,服务器端默认不需要任何配置(后面会讲例外情况)。
场景一:本地转发(-L)——访问内网服务
需求:你在家(Ubuntu),想连接公司内网的MySQL数据库(IP 192.168.1.100,端口 3306),但公司防火墙只允许内网访问。
命令:
ssh -L 3307:192.168.1.100:3306 root@123.45.67.89
效果:输入服务器密码后,隧道建立。你打开另一个终端,执行:
mysql -h 127.0.0.1 -P 3307 -u 用户名 -p
你连的是自己电脑的 3307 端口,但实际连上了内网数据库的 3306 端口。
参数解析:
-L= Local,表示在本地监听3307= 你本机随便选一个空闲端口192.168.1.100:3306= 目标服务的真实IP和端口root@123.45.67.89= 跳板机(SSH服务器)的登录信息
场景二:远程转发(-R)——暴露内网服务
需求:你在Ubuntu上跑了一个Web服务(端口 3000),想让外地的同事访问,但你家没有公网IP。
命令:
ssh -R 8080:127.0.0.1:3000 root@123.45.67.89
效果:同事访问 http://123.45.67.89:8080,实际看到的是你家Ubuntu上 3000 端口的网页。
参数解析:
-R= Remote,表示在远程服务器上监听8080= 公网服务器上对外开放的端口127.0.0.1:3000= 你本地服务的地址和端口
注意:默认情况下,服务器上的 8080 端口只监听 127.0.0.1,即只有服务器本机能访问。如果想让外网所有人访问,需要修改服务器配置(见下文“服务器端配置”)。
场景三:动态转发(-D)——SOCKS5代理
需求:你想让Ubuntu上的浏览器通过远程服务器访问所有网站(如访问受限资源)。
命令:
ssh -D 1080 root@123.45.67.89
效果:本机开启一个SOCKS5代理,地址为 127.0.0.1:1080。然后在Ubuntu的系统设置 → 网络 → 代理中设置:
- 代理类型:SOCKS5
- 主机:127.0.0.1
- 端口:1080
之后浏览器的所有流量都会经过远程服务器转发。
参数解析:
-D= Dynamic,动态代理1080= 本机SOCKS5代理端口
命令速查表
| 功能 | 命令参数 | 监听位置 | 流量方向 | 适用场景 |
|---|---|---|---|---|
| 本地转发 | -L 本地端口:目标IP:目标端口 | 你的电脑 | 你 → 跳板机 → 目标 | 访问内网服务 |
| 远程转发 | -R 远程端口:本地IP:本地端口 | 远程服务器 | 别人 → 跳板机 → 你 | 暴露内网服务 |
| 动态转发 | -D 本地端口 | 你的电脑 | 你(SOCKS5)→ 跳板机 → 任意目标 | 全局代理 |
四、让隧道更稳定:进阶技巧
1. 后台运行(-f)
加了 -f,SSH验证通过后进入后台运行,终端可以关闭:
ssh -f -L 3307:192.168.1.100:3306 root@123.45.67.89
2. 保持连接(心跳包)
防止网络波动导致隧道断开:
ssh -o ServerAliveInterval=60 -L 3307:192.168.1.100:3306 root@123.45.67.89
每60秒发一个“我还活着”的信号。
3. 生产环境推荐:autossh
autossh 会监控SSH连接,断开后自动重连,比 -f 更可靠。
安装:
sudo apt-get install autossh
使用(和ssh命令基本一样):
autossh -M 0 -f -L 3307:192.168.1.100:3306 root@123.45.67.89
4. 配置文件固化(省得每次敲长命令)
编辑 ~/.ssh/config:
Host mysql_tunnel
HostName 123.45.67.89
User root
LocalForward 3307 192.168.1.100:3306
ServerAliveInterval 60
之后只需执行:
ssh mysql_tunnel
五、服务器端到底要不要配置?
绝大多数情况下,服务器端不需要任何配置。 OpenSSH服务端默认开启了端口转发功能。
验证方法:查看服务器上的 /etc/ssh/sshd_config,默认包含:
AllowTcpForwarding yes # 允许TCP转发,默认就是yes
唯一需要改的地方:GatewayPorts
如果你使用远程转发(-R),并且想让外网所有人都能访问,需要修改服务器配置。
问题现象:执行 -R 后,在服务器上查看端口:
netstat -tunlp | grep 8080
显示 127.0.0.1:8080 ——只有服务器本机能访问,外网连不上。
解决办法:
- 编辑服务器SSH配置文件:
sudo vi /etc/ssh/sshd_config
- 找到或添加:
GatewayPorts yes
- 重启SSH服务:
sudo systemctl restart sshd
- 重新执行客户端的
-R命令,再查看端口,变成0.0.0.0:8080,所有人都能访问了。
安全提醒:GatewayPorts yes 相当于你主动声明“我确认要把内网服务暴露给公网”,安全责任由你自己承担。默认绑在 127.0.0.1 是为了防止意外泄露内网资源。
服务器防火墙
如果服务器开了防火墙(如 ufw),需要确保:
- SSH端口(22)是开放的
- 如果用
-R暴露了新端口(如8080),也需要放行
sudo ufw allow 8080/tcp
六、常见问题排查
| 报错信息 | 原因 | 解决办法 |
|---|---|---|
connect to host 123.45.67.89 port 22: Connection refused | 服务器SSH没开或IP错了 | 检查服务器状态:sudo systemctl status ssh |
Permission denied (publickey,password) | 密码或密钥不对 | 确认密码,或检查 ~/.ssh/authorized_keys |
bind: Address already in use | 本地端口被占用 | 换个端口,如 -L 3308:... |
channel_setup_fwd: ... Connection refused | 目标服务没开 | 用 telnet 目标IP 端口 测试可达性 |
| 远程转发(-R)外网无法访问 | 默认只监听127.0.0.1 | 服务器改 GatewayPorts yes |
查看隧道是否还活着
ps aux | grep ssh
看到你刚才的命令还在,就说明隧道正常。
关闭隧道
- 没加
-f:按Ctrl + C - 加了
-f:ps aux | grep ssh找进程ID,然后kill 进程ID
七、极简验证实验
不用改任何服务器配置,5分钟验证隧道原理:
在服务器上(开启一个测试服务):
nc -l 9999
(这个命令让服务器监听9999端口,输入什么就显示什么)
在本地Ubuntu:
ssh -L 8888:127.0.0.1:9999 root@服务器IP
在本地另一个终端:
nc 127.0.0.1 8888
输入任意文字,服务器那边的 nc -l 9999 终端里会同步显示。
原理:你连本地8888端口 → SSH加密传给服务器 → 服务器连自己的9999端口 → 数据返回。整个过程服务器配置没动过。
八、总结
SSH隧道的本质
通过SSH加密连接,把两台电脑的端口“接”在一起,让数据像走专线一样安全传输。
三种模式一句话记忆
-L= 本地开入口,通到目标(你去访问别人)-R= 远程开入口,通到你(别人来访问你)-D= 本地开万能入口(你去访问任何人)
服务器配置结论
- 99%的情况:服务器零配置,开箱即用
- 唯一例外:用
-R暴露服务给外网所有人,需要改GatewayPorts yes
适用场景
- 安全访问内网数据库、内部网站
- 内网穿透,让外网访问家里的服务
- 在公共Wi-Fi下加密所有流量
- 绕过网络限制访问受限资源
最后送你一句实践心法:先敲命令跑通,再回头理解原理。SSH隧道是“用中学”的技术,动手试一次,比看十篇文章都管用。
本文基于Ubuntu 20.04/22.04 + OpenSSH环境编写,其他Linux发行版通用。