Contents

SSH 隧道实战:端口转发、跳板机与反向代理的完整指南

为什么你需要掌握 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

安全注意事项

  1. 不要用 -L 0.0.0.0:端口 暴露转发端口,除非你明确需要。默认只监听 localhost 更安全
  2. 用密钥认证,禁用密码登录PasswordAuthentication no
  3. 远程转发配合 GatewayPorts yes 时要加防火墙规则,避免端口被扫描
  4. 跳板机的 SSH key 单独管理,不要和开发用的 key 混用
  5. 定期检查 ~/.ssh/known_hosts,跳板机更换后及时更新指纹

速查表

模式 参数 方向 典型场景
本地转发 -L 远程→本地 本地访问内网数据库
远程转发 -R 本地→远程 把本地服务暴露到公网
动态转发 -D 双向 SOCKS5 代理
跳板机 -J 多级 穿透多层网络

记住核心区别:-L 是把远程的东西拉到本地,-R 是把本地的东西推到远程,-D 是全流量代理。搞清楚方向,其他都是参数配置的问题。