SSH 代理从入门到起飞:一篇让你彻底搞懂端口转发的文章
约 5562 字大约 19 分钟
2026-09-07
事情是这样的
某天晚上,我在家想连公司内网的 MySQL 查点数据。VPN 偏偏这时候挂了,重启路由器、换网络、甚至重启电脑(经典三连),统统没用。
正当我准备放弃的时候,想起了一个老朋友——SSH。
一台能 SSH 上去的跳板机,一条命令,就让我舒舒服服地在家用 Navicat 连上了公司数据库。那一刻,我觉得 SSH 简直是世界上最帅的工具。
所以今天,就来聊聊这个“看起来很高深,其实一行命令就能搞起来”的技术——SSH 端口转发(也叫 SSH 隧道、SSH 代理,都是一个东西的不同叫法)。
先别慌,这玩意儿没你想的那么复杂
很多人一听到"隧道""端口转发""代理"这些词,脑袋就开始嗡嗡响。别怕,我用一个生活中的例子帮你理解。
想象一下,你住的小区门禁很严,外卖小哥进不来。但你又特别想吃楼下那家的麻辣烫。怎么办呢?
你走到小区门口,跟外卖小哥之间搭了条绳子,绳子上挂了个篮子。他把麻辣烫放篮子里,你拉回来。
这个"绳子+篮子"的组合,就是 SSH 隧道。你是你的电脑,外卖小哥是目标服务器(比如数据库),绳子是 SSH 连接,篮子就是端口转发。
核心就一句话:你通过一台能连上的中间机器(跳板机),去够到那些直接连不上的目标机器。
前置准备:你得有这些东西
在开整之前,确认一下你手上有这些家伙:
- 一台能 SSH 登录的中间服务器(也叫跳板机、堡垒机)——这是你的"绳子固定点"
- SSH 客户端——Linux/Mac 自带终端就行,Windows 10 以上也自带 OpenSSH(PowerShell 里直接敲
ssh就能用) - 登录凭证——用户名 + 密码,或者 SSH 密钥(推荐密钥,后面会说)
确认完了?别急,还有一个容易踩坑的地方。
别忘了检查服务端是否允许端口转发
很多公司的安全团队会默认关闭 SSH 的端口转发功能(后面会讲为什么)。如果你在服务器上有权限,检查一下 /etc/ssh/sshd_config 里这几项:
AllowTcpForwarding yes # 允许 TCP 端口转发,默认可能是 no
AllowAgentForwarding yes # 允许 SSH Agent 转发
PermitTunnel yes # 允许隧道设备如果这几项都是 no,后面不管你怎么折腾都会报 administratively prohibited 错误。改完之后记得重启 sshd:
sudo systemctl restart sshd没有服务器权限怎么办?
如果你没有服务器权限(比如公司运维管的),那就只能找运维大哥帮忙开了。态度好一点,一般都能搞定。
确认完了?开搞。
三种转发方式,对应三种场景
SSH 端口转发一共就三种姿势:本地转发、远程转发、动态转发。
别记名字,记场景。往下看你就懂了。
姿势一:本地端口转发(你最常用的那个)
场景: 你在家里(外网),想访问公司内网的 MySQL(IP 是 192.168.1.100,端口 3306)。你有一台跳板机 jump.company.com 可以 SSH 上去,这台跳板机能访问内网数据库。
翻译成人话就是:你够不着数据库,但跳板机够得着,你让跳板机帮你"递一下"。
一行命令:
ssh -NT -L 3307:192.168.1.100:3306 zhangsan@jump.company.com跑完之后,你打开 Navicat 或者命令行,连 localhost:3307 就能操作那个内网 MySQL 了。
mysql -h 127.0.0.1 -P 3307 -u root -p是不是有点魔法的感觉?
拆开来看这条命令:
| 参数 | 含义 |
|---|---|
-L | 本地转发(Local 的首字母,好记) |
3307 | 你本地开的端口,随便选一个没被占的 |
192.168.1.100:3306 | 目标地址和目标端口,就是你最终要访问的那台机器 |
zhangsan@jump.company.com | 跳板机的用户名和地址 |
-N | 不执行远程命令(只做端口转发),加上后命令不会卡在交互式终端 |
-T | 不分配伪终端(TTY),和 -N 搭配使用更干净 |
数据流是这样的:
所以你在本地往 3307 端口丢数据,SSH 就会帮你原封不动地送到内网数据库那里,回来也一样。就像你家的篮子——放进去的东西,外卖小哥一定能收到。
小提示
本地端口转发,90% 的日常需求都能搞定。连数据库、连 Redis、连内网的 Web 管理后台,都是这个套路。先把这一个练熟了,另外两个慢慢来。
姿势二:远程端口转发(把自己的服务暴露出去)
场景: 你在公司调试本地的 Spring Boot 应用(跑在 localhost:8080),同事说"把你那个接口发给我看看呗"。但你在内网,他没有办法直接连到你的电脑。
这时候反向操作一下——让跳板机开个端口,流量转发到你本地。
ssh -NT -R 9090:localhost:8080 zhangsan@jump.company.com跑完之后,同事访问 jump.company.com:9090,请求就会"穿越"到你本地的 8080 端口,看到你跑的应用。
拆开看:
| 参数 | 含义 |
|---|---|
-R | 远程转发(Remote) |
9090 | 在跳板机上开的端口 |
localhost:8080 | 你本地的服务地址和端口 |
数据流是反过来的:
注意事项: 默认情况下,远程转发的端口只监听 127.0.0.1(也就是只有跳板机自己能访问)。如果你想让同事从外部访问,需要在跳板机的 /etc/ssh/sshd_config 里加一行:
GatewayPorts yes然后重启 sshd:
sudo systemctl restart sshd安全提醒
远程转发相当于把你本地的服务“暴露”到公网了。调试完了记得关掉,别把自家的门一直开着让别人随便进。
姿势三:动态端口转发(变身 SOCKS 代理)
这个是我觉得最骚的操作。
场景: 你不想只转发一个端口,而是想把整台跳板机变成一个"万能代理"——浏览器的流量、各种应用的流量,全都走 SSH 隧道出去。
ssh -NT -D 1080 zhangsan@jump.company.com这一条命令,就在你本地的 1080 端口上起了一个 SOCKS5 代理服务器。
接下来,只要你的应用支持配 SOCKS5 代理,把代理地址设成 127.0.0.1,端口设成 1080,所有流量就都走 SSH 隧道了。
浏览器怎么配?
- Chrome:装个扩展叫 SwitchyOmega,新建代理情景模式,SOCKS5 填
127.0.0.1:1080,搞定 - Firefox:设置 → 网络设置 → 手动代理配置 → SOCKS 主机填
127.0.0.1,端口1080,选 SOCKS v5
配完之后你打开浏览器访问网站,请求的流程就变成:
浏览器 → 本地SOCKS代理(1080) → SSH加密隧道 → 跳板机 → 目标网站用这个方式上网,所有流量都是加密的,而且对外显示的 IP 是跳板机的 IP,不是你本机的。在某些网络受限的场景下,这一招特别好使。
进阶玩法
命令行工具(比如 curl)也支持 SOCKS5 代理:
curl --socks5-hostname 127.0.0.1:1080 https://example.com注意这里用的是 --socks5-hostname 而不是 --socks5,区别在于前者会让代理服务器去解析域名,更“干净”一些。
懒人福音:SSH Config 文件
每次都要敲一长串命令?太麻烦了。
SSH 有个配置文件 ~/.ssh/config(Windows 在 C:\Users\你的用户名\.ssh\config),可以把常用的连接预设好。
举个我自己的例子:
# 公司跳板机
Host jump
HostName jump.company.com
User zhangsan
Port 22
IdentityFile ~/.ssh/id_ed25519
ServerAliveInterval 60
ServerAliveCountMax 3
# 连内网数据库(本地转发)
Host dev-db
HostName jump.company.com
User zhangsan
IdentityFile ~/.ssh/id_ed25519
LocalForward 3307 192.168.1.100:3306
LocalForward 6379 192.168.1.101:6379
ServerAliveInterval 60
# 起SOCKS代理(动态转发)
Host my-proxy
HostName jump.company.com
User zhangsan
IdentityFile ~/.ssh/id_ed25519
DynamicForward 1080
ServerAliveInterval 60配好之后,用起来爽到飞起:
# 连跳板机
ssh jump
# 开数据库隧道
ssh dev-db -N
# 起代理
ssh my-proxy -N一行搞定,再也不用记那些端口号和 IP 了。
几个关键配置解释一下:
- Host:给连接起个别名,之后
ssh 别名就能连 - HostName:服务器的真实地址
- IdentityFile:SSH 私钥路径(用密钥登录比密码安全太多了,强烈建议配一个)
- ServerAliveInterval:每隔多少秒发一个心跳包,防止连接超时断开。这个很重要,不加的话过一会儿隧道就自己断了,你都不知道
- LocalForward:等价于
-L参数 - DynamicForward:等价于
-D参数
SSH 密钥登录:告别每次输密码
如果你每次 SSH 还要输密码,那必须先搞定密钥登录。这不仅仅是省事的问题,安全性也更高。
第一步:生成密钥对
ssh-keygen -t ed25519 -C "你的邮箱"一路回车就行,不用改默认路径。ed25519 是目前推荐的算法,比老式的 RSA 更快更安全。
第二步:把公钥传到服务器
ssh-copy-id zhangsan@jump.company.com这条命令会把你本地的公钥自动追加到服务器的 ~/.ssh/authorized_keys 文件里。
完了。之后 ssh zhangsan@jump.company.com 就不用输密码了,直接进。
Windows 用户看这里
如果是 Windows 并且 ssh-copy-id 不好使,可以手动把 ~/.ssh/id_ed25519.pub 的内容复制到服务器的 ~/.ssh/authorized_keys 文件里,效果一样。
后台运行与自动重连
隧道建好了,但终端一关就断了,总不能一直开着个终端窗口吧?
后台运行
加个 -f 参数,SSH 就会在后台跑:
ssh -f -NT -L 3307:192.168.1.100:3306 zhangsan@jump.company.com-f 是 fork 到后台的意思,加上之后你的终端还能继续用别的。
要关的话,找到进程杀掉就行:
# 找到进程号
ps aux | grep "ssh.*3307"
# 杀掉
kill 进程号自动重连(autossh)
网络波动导致隧道断了怎么办?用 autossh,它会在隧道断开后自动重连。
# Ubuntu/Debian 安装
sudo apt install autossh
# CentOS/RHEL 安装
sudo yum install autossh
# 使用
autossh -M 0 -o "ServerAliveInterval 30" -o "ServerAliveCountMax 3" \
-f -NT -L 3307:192.168.1.100:3306 zhangsan@jump.company.com-M 0 表示不用 autossh 自己的监控端口,改用 SSH 自带的 ServerAliveInterval 心跳机制,更靠谱。
常见翻车现场与解决方案
1. "端口被占了!"
bind [127.0.0.1]:3307: Address already in use说明你本地 3307 端口已经被别的东西占了。换一个端口就好:
ssh -L 3308:192.168.1.100:3306 zhangsan@jump.company.com -N或者先查一下谁占了:
# Linux/Mac
lsof -i :3307
# Windows
netstat -ano | findstr :33072. "连不上!Connection refused!"
先别急着怀疑 SSH,从最基本的开始排查:
# 1. 先看看跳板机能不能 ping 通
ping jump.company.com
# 2. 看看 SSH 端口是不是通的
telnet jump.company.com 22
# 3. 加 -v 看看 SSH 的详细日志,哪一步出了问题
ssh -v zhangsan@jump.company.com三个 -v(-vvv)可以看到更详细的调试信息,排查疑难杂症的时候非常好使。
3. "隧道建好了但连不上目标服务"
这种情况多半是目标地址写错了。记住,-L 参数里的目标地址是相对于跳板机的,不是你本地电脑。
比如跳板机和数据库在同一个内网,数据库的内网 IP 是 192.168.1.100,那目标地址就是 192.168.1.100,不是 localhost。
4. "用着用着就断了"
加上心跳配置,防止中间网络设备因为长时间没流量而切断连接:
ssh -o ServerAliveInterval=60 -o ServerAliveCountMax=3 -L 3307:192.168.1.100:3306 zhangsan@jump.company.com -N或者写到 ~/.ssh/config 里,一劳永逸:
Host *
ServerAliveInterval 60
ServerAliveCountMax 3Host * 表示对所有服务器都生效。
5. "administratively prohibited" 报错
channel 3: open failed: administratively prohibited: open failed这个就是前面说的,管理员把端口转发给禁了。去检查服务器的 sshd_config,把 AllowTcpForwarding 改成 yes。如果你没有权限,就找运维帮忙。
6. "SSH 隧道没报错,但连不上 Redis/MySQL"
隧道建好了,本地端口也在监听,但客户端连上去没反应或者直接报错。这时候别怀疑 SSH,检查一下目标服务本身:
- 服务是否只监听了
127.0.0.1?如果服务监听了0.0.0.0而你的-L目标写的是内网 IP,那没问题;反过来如果服务只监听127.0.0.1,你的-L目标地址就不能写内网 IP,得写127.0.0.1 - Redis 是否开启了密码认证?是否限制了
protected-mode? - MySQL 是否允许远程用户连接?
重要提醒
SSH 断开 = 隧道失效,本地工具会立即连不上。建议单独开一个终端窗口专门维护 SSH 隧道,别跟其他操作混在一起。
7. 为什么有些公司的安全团队会关闭端口转发?
这个间题很多人不理解,觉得"我只是想查个数据,为什么不让用?"
因为 SSH 端口转发本质上是一条"隐形隧道":
- 它可以绕过防火墙、网关、审计系统
- 本地流量进了内网,很难迫踪
- 容易被当成内网代理滥用
所以安全团队通常会这样配置:
AllowTcpForwarding no
AllowAgentForwarding no
PermitTunnel no
X11Forwarding no这不是针对你,是整体的安全策略。如果你确实需要用,走审批流程开就行。
一些实用场景
场景1:用 Navicat/DBeaver 连内网数据库
这可能是最常见的需求了:
ssh -f -N -L 3307:内网MySQL地址:3306 zhangsan@jump.company.com然后数据库工具里新建连接,地址填 127.0.0.1,端口填 3307,跟连本地数据库一模一样。
场景2:同时转发多个端口
想一次把 MySQL、Redis、RabbitMQ 都转发过来?
ssh -f -N \
-L 3307:192.168.1.100:3306 \
-L 6379:192.168.1.101:6379 \
-L 5672:192.168.1.102:5672 \
zhangsan@jump.company.com多个 -L 叠着写就行,想转发几个转发几个。
场景3:通过跳板机 SSH 到内网的其他机器
有些场景下,你需要先 SSH 到跳板机,再从跳板机 SSH 到内网的另一台机器。最简单的方式是用 -J(ProxyJump,需要 OpenSSH 7.3+):
ssh -J zhangsan@jump.company.com root@192.168.1.50一步到位,不用先登录跳板机再手动跳。
搭配端口转发也行,比如通过跳板机转发内网机器的 MySQL:
ssh -NT -L 3306:127.0.0.1:3306 -J zhangsan@jump.company.com root@192.168.1.50如果你的 SSH 版本比较老(< 7.3,没有 -J),可以用 -o ProxyCommand 替代:
ssh -NT -L 3306:127.0.0.1:3306 -o ProxyCommand="ssh zhangsan@jump.company.com -W %h:%p" root@192.168.1.50-W %h:%p 的意思是让跳板机把连接转发到目标主机的指定端口,效果跟 -J 一样,只是写法繁琐一点。
多级跳板怎么办?
现实中企业的安全架构往往更复杂,可能需要经过两台甚至三台跳板机才能到达目标机器。
二级跳板(跳两次):
# -J 后面逗号分隔,按网络进入方向写
ssh -NT -L 3306:127.0.0.1:3306 -J user1@jump1,user2@jump2 user3@target注意顺序很重要:先经过 jump1,再经过 jump2,最后到 target。写反了可就连不上了。
三级跳板:
ssh -J jump1,jump2,jump3 target同理,逗号分隔,按顺序写就行。
或者在 config 里配置,更优雅:
Host inner-server
HostName 192.168.1.50
User root
ProxyJump jump之后直接 ssh inner-server 就行了,SSH 会自动帮你走跳板机。
场景4:Windows 下的 PuTTY 用户
如果你更习惯图形界面,PuTTY 也能搞端口转发:
- 打开 PuTTY,填好跳板机的地址和端口
- 左侧菜单找到 Connection → SSH → Tunnels
- Source port 填本地端口(比如
3307) - Destination 填目标地址(比如
192.168.1.100:3306) - 选 Local,点 Add,上面的列表里会出现
L3307 → 192.168.1.100:3306 - 回到 Session 页面,点 Open 连接,输用户名密码
效果跟命令行一模一样。
场景5:服务部署在 Docker 上怎么办?
如果远程服务器上的服务是跑在 Docker 容器里的,端口转发一样能用,只是稍微有点小区别。
情况一:容器端口映射到了宿主机
比如启动容器时指定了端口映射:
docker run -d --name redis-server -p 16379:6379 redis:7这种情况下,宿主机的 16379 端口就是 Redis 服务。你直接把它当成宿主机上跑的普通服务来转发就行:
ssh -NT -L 16379:127.0.0.1:16379 root@8.145.45.70跟服务直接跑在宿主机上没有任何区别。
情况二:容器端口没有映射到宿主机
有些服务只跑在容器内部网络里,没有 -p 映射端口。这时候你需要先查到容器的内网 IP:
docker inspect redis-server | grep IPAddress
# 假设输出是 172.17.0.3然后把转发目标指向容器 IP:
ssh -NT -L 26379:172.17.0.3:6379 root@8.145.45.70这样本地 26379 端口的流量就会通过 SSH 到远程服务器,再直接打到容器的 6379 端口上。
Docker 场景的关键区别
目标地址写 127.0.0.1,指的是跳板机/宿主机本身;写容器 IP(如 172.17.0.3),指的是Docker 网络里的那台容器。搞清楚这个,Docker 场景就没什么难的了。
安全方面的几句唠叨
- 永远用密钥登录,别用密码。 密码太容易被暴力破解了,密钥就安全得多
- 远程转发用完就关。 你每多开一个端口,就多一个攻击面
- SSH 隧道用完即关。 隧道一关,本地端口立即释放,不留任何后门。用完之后记得 kill 掉进程,别让它一直挂着
- 妥善保管 SSH 私钥和密码。 私钥文件不要放到公共目录,不要提交到 Git
- 跳板机的 sshd_config 可以做限制:
# 只允许特定用户做端口转发
Match User zhangsan
AllowTcpForwarding yes
PermitOpen localhost:3306 localhost:6379
# 其他用户不允许转发
Match all
AllowTcpForwarding no- 密钥文件的权限要对:
chmod 700 ~/.ssh
chmod 600 ~/.ssh/id_ed25519
chmod 644 ~/.ssh/id_ed25519.pub权限不对的话,SSH 会拒绝使用这个密钥(这是保护你的,别嫌它烦)。
安全底线
SSH 隧道用完即关,本地端口立即释放,不留任何后门。私钥文件不要放到公共目录,绝对不要提交到 Git。
一张表总结
| 转发类型 | 参数 | 一句话解释 | 典型场景 |
|---|---|---|---|
| 本地转发 | -L | 本地端口 → 跳板机 → 目标机器 | 在家连公司内网数据库 |
| 远程转发 | -R | 跳板机端口 → 你的本地服务 | 让同事访问你本地的开发环境 |
| 动态转发 | -D | 本地 SOCKS5 代理,万能转发 | 浏览器代理上网、全局代理 |
最后说两句
SSH 端口转发这东西,说难不难,说简单也不算特别简单。关键是要理解数据的流向——谁发给谁,中间经过了什么。
搞清楚了这一点,三种转发方式其实就是一回事:
- 本地转发: 你主动伸手去够远处的东西
- 远程转发: 远处的东西通过中间人找到你
- 动态转发: 你搭了个万能中转站,谁都能通过它去任何地方
建议先从本地转发练起,把自己的开发环境配好隧道,用两天就熟了。然后再玩动态转发,最后远程转发按需使用。
别光看不练——打开终端,敲一条命令试试,比看十遍文章都管用。
自测检验
看完这篇文章,试着回答这几个问题,检验你是否真正掌握了:
- 什么是 SSH 的本地端口转发?它能帮你解决什么问题?
- 本地转发、远程转发、动态转发的区别是什么?
-N和-T参数分别做什么?- 内网服务部署在 Docker 和宿主机上,使用端口转发时有什么区别?
- 为什么有些公司的安全团队会关闭 SSH 端口转发?
对了,如果你也经历过 VPN 挂了全靠 SSH 续命的夜晚,请在评论区举个手让我知道不是只有我一个人。