大家好,我是冯哥的缓存。
上一篇我们聊了SSH 的核心用法——密钥登录、config 别名、scp/rsync 传文件。这些已经够日常使用了。
但 SSH 还有一个非常强大的功能:端口转发(Port Forwarding)。
听起来有点技术味,其实用处极实际:
·公司内网的数据库,在家怎么连?
·家里的树莓派跑了一个服务,没有公网 IP,朋友怎么访问?
·服务器上起了个Web 服务只监听 127.0.0.1,不对外开放,怎么在本机浏览器里看?
这三个场景,分别对应 SSH 端口转发的三种模式。本篇先讲前两种:本地转发-L和 远程转发 -R。
一、端口转发是什么——先搞清楚原理
1.1 SSH 隧道的比喻
SSH 端口转发,本质上是在 SSH 加密连接里"塞进去"一条数据通道。端口转发本身不改变数据内容,只是‘借道’传输。可以理解为:
在 A 和 B 之间已经打通了一条加密隧道(SSH 连接),端口转发就是在这条隧道里再开几条管道,让其他协议的数据也能借着这条隧道走。
这些借道的数据,对两端的应用来说是"透明的"——它们根本不知道自己在走 SSH 隧道,以为只是普通的 TCP 连接。
1.2 三种模式总览
模式 | 参数 | 用途简介 | 典型场景 |
本地转发 | -L | 把远端的服务映射到本机某端口 | 在家访问公司内网数据库 |
远程转发 | -R | 把本机的服务暴露到远端某端口 | 把家里树莓派的服务穿透到公网 |
动态转发 | -D | 本机开 SOCKS5 代理,流量经远端服务器出口 | 下篇讲 |
本篇重点聊-L和 -R。
二、本地转发-L——把远端服务映射到本机
2.1 原理
本机:本地端口→SSH隧道→跳板机/远端机→目标服务
在本机监听一个端口,所有连到这个端口的流量,都通过 SSH 隧道转发到远端的某个地址和端口上。
用通俗话说:就像给远端服务装了一个"代理入口"在本机。
2.2 语法
ssh -L 本地端口:目标主机:目标端口 跳板机
部分 | 含义 |
本地端口 | 在本机监听哪个端口(随便选一个没被占用的,如 13306) |
目标主机 | 从跳板机视角出发,目标服务在哪里(可以是 127.0.0.1,也可以是内网IP) |
目标端口 | 目标服务的真实端口(如 MySQL 3306、PostgreSQL 5432) |
跳板机 | SSH 连接到的那台机器(可以用 ~/.ssh/config 里的别名) |
2.3 典型场景一:访问远端数据库
场景:公司服务器上跑了 MySQL(只监听 127.0.0.1:3306,不对外开放),在家想用 Navicat 直连。
ssh -L 13306:127.0.0.1:3306 office
💡解释:
·13306:在本机监听 13306 端口(用 13306 是为了避开本机可能已经在跑的 MySQL),是在本机选一个空闲端口,如果被占用可以换其他端口。
·127.0.0.1:3306:从 office服务器来看,MySQL在 127.0.0.1:3306
·office:~/.ssh/config里配好的公司服务器别名
建立连接后,在Navicat(或任何数据库工具)里填:
·主机:127.0.0.1
·端口:13306
就连上了公司服务器上的 MySQL,全程走 SSH 加密,安全。
2.4 典型场景二:访问内网另一台机器
场景:跳板机在公网,内网还有一台机器(192.168.0.100)跑了 Web 服务(8080 端口),在本机浏览器里访问。
# 如果跳板机可以访问内网
ssh -L 18080:192.168.0.100:8080 jumpserver
建立后,浏览器访问http://127.0.0.1:18080,流量经 SSH 隧道到 jumpserver,再由 jumpserver转发到内网的 192.168.0.100:8080。
本机:18080→jumpserver→192.168.0.100:8080
如果想让局域网内其他机器也能用这个隧道,加 -g 参数
ssh -g -L 13306:127.0.0.1:3306 office
2.5 让转发在后台持续运行
每次直接ssh -L ...会打开一个交互式终端,关掉窗口隧道就断了。
方案一:后台运行,不开交互终端
ssh -L 13306:127.0.0.1:3306 office -N -f
参数 | 说明 |
-N | 不执行远程命令,只做端口转发 |
-f | 后台运行(fork 到后台) |
方案二:加-o ExitOnForwardFailure=yes防止静默失败
ssh -L 13306:127.0.0.1:3306 -N -f -o ExitOnForwardFailure=yes office
如果转发端口被占用或连接失败,这个参数会让 SSH 直接报错退出,而不是"静默成功"但隧道实际没通。
关闭后台隧道:
# 找到进程
ps aux | grep 'ssh -L'
# 杀掉对应的 PID
kill 进程号
2.6 写进 ~/.ssh/config里(高级用法)
如果某条转发经常用,可以直接写进 config,每次ssh office就自动建立转发:
Host office
HostName 47.100.xxx.xxx
User fengge
IdentityFile ~/.ssh/id_ed25519
LocalForward 13306 127.0.0.1:3306
LocalForward 18080 192.168.0.100:8080
LocalForward可以写多条,同时转发多个端口。
三、远程转发-R——把本机服务暴露到远端
3.1 原理
本地转发是"拉过来",远程转发是"推出去":
外网用户 →远端服务器:端口→SSH隧道→本机:端口
在远端服务器上监听一个端口,连到这个端口的流量,通过 SSH 隧道反向转发到本机的某个服务上。
用通俗话说:本机没有公网IP,借远端服务器的端口当"门牌号",让别人能访问到本机的服务。
3.2 语法
ssh -R 远端端口:本机目标主机:本机目标端口 远端服务器
部分 | 含义 |
远端端口 | 在远端服务器监听的端口(别人从外网访问这个端口) |
本机目标主机 | 从本机视角看,流量转到哪里(通常是 127.0.0.1) |
本机目标端口 | 本机服务的真实端口 |
远端服务器 | 有公网 IP 的那台机器 |
3.3 典型场景:内网穿透
场景:在家里的树莓派(没有公网 IP)上跑了一个 Web 服务(3000 端口),想让朋友从外网访问。有一台云服务器(公网 IP:47.100.xxx.xxx)。
在树莓派上执行:
ssh -R 8080:127.0.0.1:3000 aliyun
💡解释:
·8080:在 aliyun服务器上监听8080 端口
·127.0.0.1:3000:从树莓派(本机)视角看,服务在 127.0.0.1:3000
·aliyun:有公网 IP 的云服务器别名
💡注意:这个隧道是从树莓派发起的,连接建立后树莓派不能关机,否则隧道就断了。
建立后,朋友访问http://47.100.xxx.xxx:8080,流量经 SSH 隧道到树莓派上的 3000 端口。
朋友的浏览器→47.100.xxx.xxx:8080→SSH隧道→树莓派:3000
3.4 让远端监听所有网卡(默认只监听 127.0.0.1)
SSH 远程转发默认只在远端的 127.0.0.1上监听,也就是说外网访问不到,只有远端服务器本机才能访问。 如果远端需要监听 80/443 等低端口,需要用 root 用户执行,或者在 sshd_config 里设置 PermitRootLogin yes(不推荐)。
要让外网也能访问,需要两步:
第一步:-R参数里指定 0.0.0.0(监听所有网卡):
ssh -R 0.0.0.0:8080:127.0.0.1:3000 aliyun
第二步:在远端服务器的 /etc/ssh/sshd_config里开启这个选项:
GatewayPorts yes
修改后重启SSH 服务:
sudo systemctl restart sshd
⚠️注意:GatewayPorts yes会让任何 SSH 远程转发都能绑定外网接口,yes 是最常见也最宽松的配置,需要结合防火墙控制哪些端口对外开放,避免意外暴露服务。
3.5 后台运行
和本地转发一样,加-N -f后台运行:
ssh -R 0.0.0.0:8080:127.0.0.1:3000 -N -f aliyun
3.6 连接断了怎么保活
SSH 远程转发的一个坑:网络波动断开后,隧道不会自动重连,需要手动处理。
方法一:AutoSSH(推荐)
autossh会自动监控 SSH 连接,断了自动重连:
# 安装 autossh
sudo apt install autossh# Debian/Ubuntu
sudo pacman -S autossh# Arch
# 如果 apt 没有 autossh,可以 sudo apt install autossh 或从源码编译,但大多数官方源都有。
# 使用 autossh 建立持久隧道
autossh -M 0 -N -o "ServerAliveInterval 30" -o "ServerAliveCountMax 3" \
-R 0.0.0.0:8080:127.0.0.1:3000 aliyun
参数 | 说明 |
-M 0 | 禁用 autossh 自带的监控端口(用 ServerAlive 心跳代替) |
-N | 不执行远程命令 |
ServerAliveInterval 30 | 每 30 秒发一次心跳 |
ServerAliveCountMax 3 | 3 次心跳失败后断开并重连 |
方法二:systemd服务(长期运行)
新建服务文件/etc/systemd/system/ssh-tunnel.service:
[Unit]
Description=SSH Remote Tunnel
After=network.target
[Service]
User=你的用户名
ExecStart=/usr/bin/autossh -M 0 -N \
-o "ServerAliveInterval=30" \
-o "ServerAliveCountMax=3" \
-o "ExitOnForwardFailure=yes" \
-R 0.0.0.0:8080:127.0.0.1:3000 aliyun
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
启用并启动:
sudo systemctl enable ssh-tunnel
sudo systemctl start ssh-tunnel
这样树莓派开机就自动建立隧道,断了也自动重连,完全不用手动管理。
四、本地转发vs 远程转发对比
对比项 | 本地转发 -L | 远程转发 -R |
方向 | 把远端服务"拉"到本机 | 把本机服务"推"到远端 |
谁发起 SSH | 本机(想访问远端服务的那端) | 本机(提供服务的那端) |
适合场景 | 在家访问公司内网/数据库 | 没有公网IP的本机对外暴露服务 |
远端需要开什么 | 无特殊要求 | 若要外网可访问,需 GatewayPorts yes |
保活方案 | autossh 或 systemd | autossh 或 systemd |
选择哪种转发,关键看‘谁有公网 IP’——如果远端有公网IP 用 -L,如果本机没有公网 IP 用 -R。
五、常见问题排查
问题 | 可能原因 | 解决方法 |
本地端口连上但没响应 | 目标服务没在跑 / 目标地址写错 | 先在跳板机上直接 telnet 127.0.0.1 3306 测试 |
远程转发后外网连不上 | GatewayPorts 未开启 | 检查远端 sshd_config 里 GatewayPorts yes |
隧道一会儿就断 | 没有心跳配置 | 加 ServerAliveInterval 60 到 ssh 命令或 config |
bind: Address already in use | 本地/远端端口被占 | 换一个没被占用的端口 |
autossh 看起来在跑但隧道没通 | autossh 默认 -M 端口被占 | 改用 -M 0 配合 ServerAliveInterval |
建立隧道后关掉终端就断了 | 忘了加 -f | 补加 -f -N,或用 systemd 管理 |
六、速查表
本地转发-L
操作 | 命令 |
基本本地转发 | ssh -L 本地端口:目标IP:目标端口 跳板机 |
后台运行 | ssh -L ... -N -f 跳板机 |
访问远端 MySQL | ssh -L 13306:127.0.0.1:3306 -N -f server |
访问内网 Web | ssh -L 18080:192.168.0.100:8080 -N -f jumpserver |
远程转发-R
操作 | 命令 |
基本远程转发 | ssh -R 远端端口:127.0.0.1:本机端口 远端服务器 |
对外网开放 | ssh -R 0.0.0.0:远端端口:127.0.0.1:本机端口 远端 |
后台运行 | ssh -R ... -N -f 远端服务器 |
autossh 保活 | autossh -M 0 -N -o "ServerAliveInterval 30" -R ... 远端 |
💡提示:选择哪种转发,记住一句话——想"拿过来"用 -L,想"推出去"用 -R。
下篇预告:《 Linux SSH端口转发(下)—— -D动态转发 》