SSH 服务器完全指南 / 第7章 SSH 隧道与端口转发
第7章 SSH 隧道与端口转发
7.1 SSH 隧道概述
SSH 隧道(Tunneling)是将任意网络流量通过 SSH 加密通道传输的技术。它可以让你安全地访问无法直接到达的服务。
三种转发类型
| 类型 | 方向 | 命令 | 典型用途 |
|---|---|---|---|
| 本地转发 | 本地 → 远程 | -L | 访问远程内网服务 |
| 远程转发 | 远程 → 本地 | -R | 将本地服务暴露到外网 |
| 动态转发 | 通过远程代理 | -D | SOCKS 代理上网 |
7.2 本地端口转发(Local Forwarding)
基本语法
ssh -L [本地地址:]本地端口:目标地址:目标端口 用户@SSH服务器
工作原理
[本地客户端] → [SSH 服务器] → [目标服务]
:本地端口 转发 :目标端口
浏览器访问 localhost:8080
↓ SSH 加密隧道
SSH 服务器转发到
↓
localhost:80(在 SSH 服务器看来的 localhost)
常用示例
示例一:访问远程 Web 服务
# 访问远程服务器的 80 端口
ssh -L 8080:localhost:80 user@server
# 现在浏览器访问 http://localhost:8080 = 远程服务器的 80 端口
示例二:访问远程数据库
# 通过 SSH 隧道访问远程 MySQL
ssh -L 3306:localhost:3306 user@db-server
# 本地 mysql 客户端连接
mysql -h 127.0.0.1 -P 3306 -u root -p
示例三:访问内网服务
# 通过跳板机访问内网 Web 应用
ssh -L 8080:10.0.0.50:8080 user@bastion
# 访问本地 8080 = 内网 10.0.0.50 的 8080
示例四:多个端口转发
# 同时转发多个端口
ssh -L 8080:localhost:80 -L 3306:localhost:3306 -L 6379:localhost:6379 user@server
示例五:指定绑定地址
# 只绑定到 localhost(默认,更安全)
ssh -L localhost:8080:localhost:80 user@server
# 绑定到所有接口(允许局域网其他机器访问)
ssh -L 0.0.0.0:8080:localhost:80 user@server
# 绑定到特定 IP
ssh -L 192.168.1.100:8080:localhost:80 user@server
示例六:通过配置文件
# ~/.ssh/config
Host db-tunnel
HostName db-server.example.com
User admin
LocalForward 3306 localhost:3306
LocalForward 6379 localhost:6379
RequestTTY no
ExitOnForwardFailure yes
# 使用
ssh db-tunnel -fN
本地转发参数详解
ssh -L [bind_address:]port:host:hostport user@server
# bind_address - 本地绑定地址(默认 127.0.0.1)
# port - 本地监听端口
# host - 远程目标主机(从 SSH 服务器的角度)
# hostport - 远程目标端口
关键理解:
host:hostport是从 SSH 服务器的角度看的目标。如果写localhost:3306,指的是 SSH 服务器的 localhost:3306。
7.3 远程端口转发(Remote Forwarding)
基本语法
ssh -R [远程地址:]远程端口:本地地址:本地端口 用户@SSH服务器
工作原理
[外部客户端] → [SSH 服务器] → [本地机器]
访问 :远程端口 :本地端口
外部访问 server:9090
↓ SSH 加密隧道
转发到本地机器的 localhost:3000
常用示例
示例一:将本地开发服务暴露到公网
# 本地运行的开发服务器在 3000 端口
# 通过远程服务器暴露到公网 9090 端口
ssh -R 9090:localhost:3000 user@server
# 现在任何人访问 server:9090 都会转发到你本地的 3000 端口
示例二:内网穿透
# 你的开发机在 NAT 后面,需要让外部访问
ssh -R 8080:localhost:80 user@公网服务器
# 需要 GatewayPorts yes(在服务器 sshd_config 中)
示例三:远程调试
# 将本地调试端口暴露给远程服务器
ssh -R 5005:localhost:5005 user@remote-dev-server
服务端配置要求
# /etc/ssh/sshd_config
# 启用远程转发
AllowTcpForwarding yes
# 允许远程转发监听所有接口(默认只监听 localhost)
GatewayPorts yes
# 或
GatewayPorts clientspecified # 由客户端指定
远程转发安全注意事项
# 默认安全行为:远程端口只绑定到 127.0.0.1
ssh -R 9090:localhost:3000 user@server
# 只有服务器本机可以访问 server:9090
# 如果需要外部访问,客户端指定绑定地址
ssh -R 0.0.0.0:9090:localhost:3000 user@server
# 需要服务端 GatewayPorts clientspecified
⚠️ 警告: 远程端口转发将你的本地服务暴露到远程服务器。确保你了解安全风险,不要暴露敏感服务。
7.4 动态端口转发(SOCKS 代理)
基本语法
ssh -D [本地地址:]本地端口 用户@SSH服务器
工作原理
[本地应用] → [SOCKS 代理 :1080] → [SSH 服务器] → [互联网]
浏览器 动态转发 加密隧道 任意目标
与本地转发的区别:
- 本地转发:固定目标地址和端口
- 动态转发:代理任意目标(SOCKS 协议)
常用示例
示例一:创建 SOCKS5 代理
# 创建 SOCKS5 代理在本地 1080 端口
ssh -D 1080 user@server
# 后台运行
ssh -D 1080 -fN user@server
示例二:浏览器配置
# Firefox 配置
# 设置 → 网络设置 → 手动代理配置
# SOCKS 主机:127.0.0.1 端口:1080
# 选择 SOCKS v5
# 或使用命令行启动 Firefox
# Firefox
MOZ_PROXY="socks5://127.0.0.1:1080" firefox
# Chrome
google-chrome --proxy-server="socks5://127.0.0.1:1080"
示例三:全系统代理
# 设置环境变量(部分应用支持)
export ALL_PROXY=socks5://127.0.0.1:1080
export http_proxy=socks5://127.0.0.1:1080
export https_proxy=socks5://127.0.0.1:1080
# 或使用 tsocks/proxychains
# /etc/proxychains.conf
[ProxyList]
socks5 127.0.0.1 1080
proxychains curl https://api.example.com
示例四:通过代理使用 Git
# ~/.gitconfig
[http]
proxy = socks5://127.0.0.1:1080
[https]
proxy = socks5://127.0.0.1:1080
# 或使用环境变量
export GIT_PROXY_COMMAND="proxychains"
7.5 高级隧道场景
场景一:多跳隧道
# 通过两台跳板机访问目标
# 本地 → bastion1 → bastion2 → target
# 方式一:ProxyJump(推荐)
ssh -J user1@bastion1,user2@bastion2 user3@target
# 方式二:链式本地转发
ssh -L 2222:bastion2:22 user1@bastion1
# 然后
ssh -p 2222 -L 3333:target:22 user2@localhost
# 最后
ssh -p 3333 user3@localhost
场景二:自动重连隧道
#!/bin/bash
# auto-tunnel.sh - 自动重连的 SSH 隧道
LOCAL_PORT=3306
REMOTE_HOST=localhost
REMOTE_PORT=3306
SSH_HOST=user@server
while true; do
echo "$(date): Starting tunnel..."
ssh -L $LOCAL_PORT:$REMOTE_HOST:$REMOTE_PORT \
-N \
-o ServerAliveInterval=60 \
-o ServerAliveCountMax=3 \
-o ExitOnForwardFailure=yes \
-o ConnectTimeout=10 \
$SSH_HOST
echo "$(date): Tunnel disconnected. Reconnecting in 5s..."
sleep 5
done
场景三:systemd 管理的持久隧道
# /etc/systemd/system/ssh-tunnel-mysql.service
[Unit]
Description=SSH Tunnel to MySQL
After=network-online.target
Wants=network-online.target
[Service]
User=tunnel-user
ExecStart=/usr/bin/ssh -N -L 3306:localhost:3306 -o ServerAliveInterval=60 -o ServerAliveCountMax=3 -o ExitOnForwardFailure=yes db-admin@db-server
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
# 启动服务
sudo systemctl enable ssh-tunnel-mysql
sudo systemctl start ssh-tunnel-mysql
sudo systemctl status ssh-tunnel-mysql
场景四:临时反向隧道(类似 ngrok)
# 将本地 Web 服务临时暴露
ssh -R 80:localhost:3000 serveo.net
# 或
ssh -R 80:localhost:3000 localhost.run
# 获得一个公网 URL 如 https://xxxxx.lhr.life
7.6 转发管理与调试
查看活跃隧道
# 查看 SSH 进程
ps aux | grep "ssh -"
# 查看端口监听
ss -tlnp | grep ssh
# 或
netstat -tlnp | grep ssh
# 查看具体隧道连接
lsof -i -n | grep ssh
控制 Socket
# 使用 -M 和 -S 创建控制 socket
ssh -M -S /tmp/ssh-tunnel-ctl -L 8080:localhost:80 -fN user@server
# 检查隧道状态
ssh -S /tmp/ssh-tunnel-ctl -O check user@server
# 终止隧道
ssh -S /tmp/ssh-tunnel-ctl -O exit user@server
# 暂停/恢复
ssh -S /tmp/ssh-tunnel-ctl -O stop user@server
ssh -S /tmp/ssh-tunnel-ctl -O start user@server
调试转发问题
# 使用 -v 查看详细信息
ssh -v -L 8080:localhost:80 user@server
# 关键调试信息:
# debug1: Local forwarding listening on 127.0.0.1 port 8080.
# debug1: channel 0: new [client-session]
# debug1: Connection to port 8080 forwarding to localhost port 80 requested.
# 检查端口是否被占用
ss -tlnp | grep :8080
# ExitOnForwardFailure - 转发失败时退出
ssh -L 8080:localhost:80 -o ExitOnForwardFailure=yes -N user@server
7.7 安全最佳实践
| 实践 | 说明 |
|---|---|
| 绑定 localhost | 转发端口默认只监听 127.0.0.1 |
| 使用 -N | 不打开 Shell,减少攻击面 |
| 使用 -f | 后台运行,适合长期隧道 |
| ExitOnForwardFailure | 转发失败时退出 |
| ServerAliveInterval | 保持连接活性 |
| 控制允许的转发 | 服务端 AllowTcpForwarding、PermitOpen |
限制可转发的目标
# /etc/ssh/sshd_config
# 允许转发到特定地址
Match User deploy
AllowTcpForwarding local
PermitOpen localhost:3000 localhost:8080 10.0.0.50:5432
# 只允许转发到上述地址
# 禁止所有转发
Match Group restricted
AllowTcpForwarding no
PermitOpen none
扩展阅读
下一章: 第8章 X11 与 Agent 转发 → 学习图形界面转发和 SSH Agent 安全使用。