系列:Linux 命令基础
- Tmux 入门教程:让 SSH 断线和多任务终端不再中断
- Tree 命令教程:快速查看和导出目录结构
- Linux 搜索命令教程:find、grep、locate、rg 应该怎么选
- Linux 压缩与解压命令教程:tar、zip、gzip、xz、zstd 一次讲清
- Linux 文本处理命令教程:cat、less、head、tail、wc、sort、uniq
- Linux 权限命令教程:chmod、chown、umask
- Linux 进程查看与管理:ps、top、htop、kill、jobs
- Linux 网络排查命令:ss、curl、dig、ping、traceroute
- systemctl 与 journalctl 入门:管理服务和查看日志
- rsync 使用指南:服务器文件同步与备份
- Linux 服务器安全基础:SSH、用户权限、防火墙和 fail2ban
- 线上问题排查流程:日志、端口、进程、磁盘、网络怎么一步步看
- 深入浅出:Linux 树状目录神器 tree 命令的安装与使用指南
- Linux 系统 Vim 编辑器终极指南:从入门到精通
- SSH 隧道实战:本地转发、远程转发和动态转发
- SSH Config 使用指南:别名、跳板机与多服务器管理
背景
在日常开发中,经常会遇到需要访问服务器内部网络服务的场景:比如数据库只监听了 127.0.0.1、内网的 Web 管理界面、或者需要通过跳板机才能连接的后端服务。如果每次都登录到服务器上用 curl 或者开个反向代理,效率很低。
SSH 本身除了远程登录,还内置了一套端口转发的机制,可以在加密通道里把远程端口映射到本地,或者把本地端口暴露给远程服务器,甚至可以充当临时的 SOCKS 代理。这些功能在 SSH 客户端中通过 -L、-R、-D 三个参数实现,不依赖额外软件,只要服务器开启了 SSH 服务就能用。
前置条件:已掌握基本的 SSH 登录和密钥配置,如果还不熟悉,可以参考博客内的 Linux 服务器安全基础:SSH、用户权限、防火墙和 fail2ban 和 SCP 和 SFTP 使用指南:远程文件传输基础。
三种转发模式一张表
不同参数对应不同的流量方向,先通过一张表建立整体概念:
| 参数 | 全称 | 方向 | 典型场景 |
|---|---|---|---|
-L | Local Forward | 本地 → 远程 | 在本地访问远程服务器的内部服务 |
-R | Remote Forward | 远程 → 本地 | 让远程服务器访问本地开发环境 |
-D | Dynamic Forward | 本地 → 远程(多目标) | 通过跳板机充当 SOCKS 代理上网 |
下面通过具体例子把每一种模式讲清楚。
本地转发(-L):把远程内部服务拉到本地
数据库出于安全考虑,通常只监听 127.0.0.1,不对外开放端口。如果要用本地的数据库管理工具去连接,就可以用本地转发。
命令格式:
ssh -L [本地地址:]本地端口:目标主机:目标端口 用户@跳板机一个实际例子:远程服务器 192.168.1.10 上跑着 MySQL,只监听了 127.0.0.1:3306。在本地执行:
ssh -L 3307:127.0.0.1:3306 user@192.168.1.10这条命令会在本地监听 3307 端口,所有发给 localhost:3307 的流量都会经过 SSH 隧道转发到 192.168.1.10 这台机器上,再由它去连接 127.0.0.1:3306。之后在本地就可以用 MySQL 客户端连接 127.0.0.1:3307:
mysql -h 127.0.0.1 -P 3307 -u root -p如果希望局域网内的其他机器也能通过本机访问这个隧道(比如你在办公室想让同事调试),可以把监听地址绑到 0.0.0.0:
ssh -L 0.0.0.0:3307:127.0.0.1:3306 user@192.168.1.10但要确保 SSH 服务端的 GatewayPorts 配置已启用(默认为 no),否则绑定 0.0.0.0 会失败。可以在远程服务器的 /etc/ssh/sshd_config 中设置 GatewayPorts yes 并重启 sshd,或保持默认绑定 127.0.0.1,只在本机使用。更多细节见 OpenSSH 手册 ssh_config。
本地转发也可以用于通过跳板机访问更深层的内网服务。例如本地 -> 跳板机A -> 目标主机B的3389端口(Windows RDP):
ssh -L 3389:192.168.2.20:3389 user@跳板机A这时在本地连 127.0.0.1:3389,实际经过跳板机A访问到了内网 Windows 主机。
远程转发(-R):让远程服务器访问本地的服务
和 -L 相反,-R 会把远程服务器上的一个端口流量转发回本地。经典场景是你在本地开发了一个 Web 应用,想让客户或同事临时访问,而本地没有公网 IP。
命令格式:
ssh -R [远程地址:]远程端口:本地主机:本地端口 用户@远程服务器假设本地运行着一个 Flask 开发服务器,监听 127.0.0.1:5000。有一台公网服务器 server.example.com,你想让它把公网端口 8080 转回本地 5000:
在本地执行:
ssh -R 8080:127.0.0.1:5000 user@server.example.com连接成功后,在远程服务器上会监听 8080 端口。任何人访问 http://server.example.com:8080,流量会经由 SSH 隧道转发到你本地电脑的 5000 端口。
同样,如果想要外部能访问,需要修改远程服务器的 sshd_config,设置 GatewayPorts yes 或 GatewayPorts clientspecified,否则默认只监听 127.0.0.1,只能从远程服务器本机用 curl http://127.0.0.1:8080 访问。配置参考 OpenSSH 手册 sshd_config。
注意:如果本地 5000 端口没有明确绑定 0.0.0.0,没关系,隧道里的 127.0.0.1:5000 指向的是本地服务,不需要外部可达。
动态转发(-D):使用 SSH 作为 SOCKS 代理
前面两种都是端口对端口的映射。如果需要访问远程网络里的多个服务,或者想把远程服务器当成跳板机来让浏览器上网,可以使用动态转发。
命令格式:
ssh -D [本地地址:]本地端口 用户@远程服务器例如:
ssh -D 1080 user@server.example.com这条命令会在本地起一个 SOCKS5 代理,监听 127.0.0.1:1080。之后在系统或浏览器中设置 SOCKS 代理指向 127.0.0.1:1080,所有流量就会经过远程服务器 server.example.com 出去。
在 Linux 终端中让命令行工具走 SOCKS 代理,可以用 proxychains 或 tsocks:
sudo apt install proxychains4编辑 /etc/proxychains4.conf,在最后添加一行:
socks5 127.0.0.1 1080然后运行:
proxychains4 curl http://example.com浏览器中可以安装 SwitchyOmega 之类的插件,按规则切换代理。更详细的 SOCKS 代理配置可参考 Dynamic Port Forwarding 文档。
这个模式对通过国外跳板机访问某些网络服务很实用,也能用于绕过内网限制直接访问目标内网的多个服务,而不像 -L 那样要写很多条 forward。
保持隧道稳定和后台运行
直接在终端中执行 ssh -L/R/D 会占用一个会话,退出后隧道断开。想让隧道持久化,有几个方法:
方法一:添加 -N -f 参数
-N:不执行远程命令,只做端口转发-f:认证完成后把 SSH 进程放入后台
完整示例:
ssh -f -N -L 3307:127.0.0.1:3306 user@server方法二:使用 autossh 自动重连
网络不稳定时隧道断了不会自己恢复,可以用 autossh 监控并自动重连:
sudo apt install autosshautossh -M 0 -f -N -L 3307:127.0.0.1:3306 user@server-M 0 表示不用监控端口,只用子进程检查。后面接的 -f -N -L 和普通 ssh 一样。autossh 的可靠性比手动重连高很多。
方法三:写成 systemd 服务
这样服务器重启后也能自动拉起隧道:
sudo nano /etc/systemd/system/ssh-tunnel.service写入:
[Unit]Description=SSH Tunnel to Remote MySQLAfter=network.target
[Service]User=yourlocaluserExecStart=/usr/bin/ssh -N -L 3307:127.0.0.1:3306 user@remote-serverRestart=alwaysRestartSec=10
[Install]WantedBy=multi-user.target然后启用:
sudo systemctl enable --now ssh-tunnel.service常见问题
1. 转发端口被占用
启动隧道时提示 bind [::1]:3307: Address already in use,说明本地端口已被别的程序占用。可以换个本地端口,或先 sudo lsof -i :3307 找到进程并决定是否释放。
2. 远程转发绑定了 127.0.0.1,外网无法访问
这就是前面提到的 GatewayPorts 配置问题。安全考虑默认拒绝绑定 0.0.0.0。在远程服务器 /etc/ssh/sshd_config 中加入 GatewayPorts clientspecified,然后重启 sshd。之后客户端执行 ssh -R 0.0.0.0:8080:localhost:5000 user@server 就会监听在所有接口上。
3. 隧道空闲一段时间自动断开
SSH 会探活检测连接,但有时防火墙或 NAT 设备会清理空闲 TCP 连接。在客户端 ~/.ssh/config 中增加保活设置:
Host * ServerAliveInterval 60 ServerAliveCountMax 3这样每 60 秒发一次心跳包,3 次失败后断开。或者用 autossh 自动重连。
4. 使用跳板机链式转发
有时源服务器不能直接访问目标服务器,需要通过跳板机。但端口转发不能像登录一样一条命令链式,可采用嵌套:先建立一个到跳板机的本地转发,再通过这个隧道建立第二个转发。或者在 ~/.ssh/config 中配置 ProxyJump,然后直接执行本地转发,SSH 会自动通过跳板机。
总结
三种 SSH 端口转发覆盖了绝大部分临时网络访问需求:-L 把远程服务拉回本地,-R 把本地服务暴露给远程,-D 把远程机器变成代理访问多个资源。理解流量方向是掌握这三种方式的关键,不必死记参数,对照那张方向表多练习几次自然就熟了。
日常工作中,autossh 配合 systemd 可以保证关键隧道的稳定运行,配合 SSH Config 中的 ServerAliveInterval 几乎能做到无人值守。如果需要更复杂的组网,考虑专业 VPN 方案;但临时调试或偶尔访问内部服务,SSH 隧道仍然是最轻便的工具。