为什么你需要掌握 SSH 隧道
日常开发中,你大概率遇到过这些场景:本地调试需要连接内网数据库、生产服务器只允许跳板机访问、内网服务需要临时暴露给外部测试。很多人第一反应是改防火墙规则或装个 frp,但其实 SSH 自带的三种端口转发就能解决绝大多数问题,无需额外工具。
本地端口转发(-L):把远程端口映射到本地
最常用的模式。你的本地机器访问不了内网的 Redis,但能 SSH 到一台跳板机,跳板机能访问 Redis。
1
2
3
4
5
6
7
8
|
# 语法:ssh -L [本地地址:]本地端口:目标地址:目标端口 user@跳板机
ssh -L 6379:redis.internal:6379 user@jump-host
# 指定本地监听地址(默认只监听 127.0.0.1)
ssh -L 0.0.0.0:6379:redis.internal:6379 user@jump-host
# 后台运行,不打开 shell
ssh -fNL 6379:redis.internal:6379 user@jump-host
|
执行后,本地 localhost:6379 就等于 redis.internal:6379。用 redis-cli 连接本地 6379 即可操作内网 Redis。
关键点:目标地址是从跳板机的视角解析的,不是你本地。所以 redis.internal 必须是跳板机能解析到的地址。
远程端口转发(-R):把本地端口暴露到远程
反向操作。你本地跑了个 Web 服务,想让远程服务器临时访问。典型场景:开发微信回调接口,需要公网地址接收请求。
1
2
3
4
5
6
|
# 语法:ssh -R [远程地址:]远程端口:本地地址:本地端口 user@远程服务器
ssh -R 8080:localhost:3000 user@remote-server
# 让远程所有 IP 都能访问(默认只监听远程的 localhost)
# 需要在远程 sshd_config 设置 GatewayPorts yes
ssh -R 0.0.0.0:8080:localhost:3000 user@remote-server
|
执行后,访问 remote-server:8080 就会转发到你本地的 3000 端口。
坑点:默认远程转发只监听 127.0.0.1,外部访问不了。需要在远程服务器的 /etc/ssh/sshd_config 中设置 GatewayPorts yes,然后重启 sshd。
动态端口转发(-D):SOCKS 代理
一键搭建 SOCKS5 代理,所有流量走 SSH 隧道。比 VPN 轻量得多。
1
2
3
4
5
|
# 语法:ssh -D [本地地址:]本地端口 user@远程服务器
ssh -D 1080 user@remote-server
# 后台运行
ssh -fND 1080 user@remote-server
|
然后在浏览器或应用中配置 SOCKS5 代理 127.0.0.1:1080,所有流量都会通过远程服务器出去。
1
2
3
4
5
|
# curl 走 SOCKS5 代理
curl --socks5 127.0.0.1:1080 https://internal-api.example.com
# git 走 SOCKS5 代理
git config --global socks.proxy "socks5://127.0.0.1:1080"
|
ProxyJump:多级跳板的标准做法
以前连内网机器要在 ~/.ssh/config 里配一堆 ProxyCommand,现在用 ProxyJump 一行搞定。
1
2
3
4
5
6
7
8
9
10
11
12
|
# 命令行直接用 -J
ssh -J jump-host target.internal
# 多级跳板
ssh -J jump1,jump2 target.internal
# 在 ~/.ssh/config 中配置
Host target
HostName target.internal
User app
ProxyJump jump-user@jump-host
# 等价于先 ssh 到 jump-host,再从 jump-host ssh 到 target.internal
|
配好之后直接 ssh target 就行,SSH 会自动经过跳板机。
~/.ssh/config 实用配置模板
把常用连接都写进配置文件,告别又长又难记的命令行参数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
# === 基础配置 ===
Host *
ServerAliveInterval 60
ServerAliveCountMax 3
ControlMaster auto
ControlPath ~/.ssh/sockets/%r@%h:%p
ControlPersist 10m
# === 跳板机 ===
Host jump
HostName jump.example.com
User deploy
Port 2222
IdentityFile ~/.ssh/jump_key
# === 内网服务(经过跳板机)===
Host db-internal
HostName 10.0.1.100
User app
ProxyJump jump
# === 本地转发(连接时自动建立隧道)===
Host db-tunnel
HostName 10.0.1.100
User app
ProxyJump jump
LocalForward 5432 10.0.1.100:5432
# === 远程转发 ===
Host expose-dev
HostName dev-server.example.com
User deploy
RemoteForward 8080 localhost:3000
|
ControlMaster 配置很重要:同一个服务器的多个 SSH 连接复用一个 TCP 连接,第二次连接几乎瞬间完成。先确保 ~/.ssh/sockets/ 目录存在:mkdir -p ~/.ssh/sockets。
autossh:保持隧道不断
SSH 隧道断开后不会自动重连。生产环境用 autossh 替代:
1
2
3
4
5
6
7
8
9
|
# 安装
sudo apt install autossh # Debian/Ubuntu
brew install autossh # macOS
# 用法:把 ssh 换成 autossh,加 M 参数指定监控端口
autossh -M 0 -fND 1080 user@remote-server
# -M 0 表示禁用监控端口,依赖 ServerAliveInterval 检测断连
# 写成 systemd service 更可靠
|
systemd service 示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
[Unit]
Description=SSH Tunnel
After=network-online.target
[Service]
User=myuser
ExecStart=/usr/bin/autossh -M 0 -ND 1080 user@remote-server
Restart=always
RestartSec=5
Environment=AUTOSSH_GATETIME=0
[Install]
WantedBy=multi-user.target
|
安全注意事项
- 不要用
-L 0.0.0.0:端口 暴露转发端口,除非你明确需要。默认只监听 localhost 更安全
- 用密钥认证,禁用密码登录。
PasswordAuthentication no
- 远程转发配合
GatewayPorts yes 时要加防火墙规则,避免端口被扫描
- 跳板机的 SSH key 单独管理,不要和开发用的 key 混用
- 定期检查
~/.ssh/known_hosts,跳板机更换后及时更新指纹
速查表
| 模式 |
参数 |
方向 |
典型场景 |
| 本地转发 |
-L |
远程→本地 |
本地访问内网数据库 |
| 远程转发 |
-R |
本地→远程 |
把本地服务暴露到公网 |
| 动态转发 |
-D |
双向 |
SOCKS5 代理 |
| 跳板机 |
-J |
多级 |
穿透多层网络 |
记住核心区别:-L 是把远程的东西拉到本地,-R 是把本地的东西推到远程,-D 是全流量代理。搞清楚方向,其他都是参数配置的问题。