SSH 隧道 —— 不装软件打通内网
SSH 隧道 —— 不装软件打通内网
分类:12 隧道与高可用 适用:OpenSSH 客户端/服务端,全平台 一句话:只要有 SSH,就能做端口转发、SOCKS 代理、反向暴露,临时排障最快的手段。
1. 它解决什么问题
- 内网 MySQL 不能公网访问,本地客户端要连。
- 家里/出差要访问公司内网 Web。
- 内网机器没有公网 IP,要让外部访问它的服务。
- 临时绕过网络策略做联调(需授权)。
2. 三种转发
| 类型 | 参数 | 方向 | 典型用途 |
|---|---|---|---|
| 本地转发 | -L |
本地端口 → 远端可达的地址 | 连内网数据库 |
| 远程转发 | -R |
远端端口 → 本地/本地可达的地址 | 暴露内网服务 |
| 动态转发 | -D |
本地 SOCKS 代理 | 浏览器走内网 |
2.1 本地转发 -L
ssh -L 13306:10.0.0.21:3306 ops@jump.example.com -N
含义:访问本机 127.0.0.1:13306 → 经 jump → 连 10.0.0.21:3306。
# 连接
mysql -h 127.0.0.1 -P 13306 -u app -p
# 多个转发
ssh -L 13306:db1:3306 -L 16379:redis1:6379 ops@jump -N
# 后台运行
ssh -f -N -L 13306:db1:3306 ops@jump
# 绑定到所有网卡(让同事也能连)
ssh -g -L 13306:db1:3306 ops@jump -N
-N 不执行远程命令,-f 后台,-g 允许非本机连接(有安全风险)。
2.2 远程转发 -R
ssh -R 8080:127.0.0.1:80 ops@public-server -N
含义:访问公网服务器的 8080 → 经隧道 → 连你本地的 80。
服务端需要允许:
# /etc/ssh/sshd_config
GatewayPorts yes # 允许绑定 0.0.0.0,默认只绑 127.0.0.1
否则只能在服务器本机访问 127.0.0.1:8080。
2.3 动态转发 -D(SOCKS5)
ssh -D 1080 -N ops@jump
curl --socks5-hostname 127.0.0.1:1080 http://internal.example.com
# 浏览器设置 SOCKS5 127.0.0.1:1080
--socks5-hostname 让 DNS 也在远端解析,内网域名才能通。
3. 常用参数
| 参数 | 作用 |
|---|---|
-N |
不执行远程命令(纯转发) |
-f |
后台运行 |
-T |
不分配 TTY |
-C |
压缩(慢链路有效) |
-g |
允许其他主机连接本地转发端口 |
-o ExitOnForwardFailure=yes |
转发失败就退出(脚本必加) |
-o ServerAliveInterval=30 |
保活探测 |
-o ServerAliveCountMax=3 |
连续 3 次无响应断开 |
-o TCPKeepAlive=yes |
TCP 层保活 |
-J jump |
经跳板机(等价 ProxyJump) |
-4 / -6 |
强制 IPv4/IPv6 |
4. 写进 ~/.ssh/config(免记长命令)
Host db-tunnel
HostName jump.example.com
User ops
LocalForward 13306 10.0.0.21:3306
LocalForward 16379 10.0.0.22:6379
ExitOnForwardFailure yes
ServerAliveInterval 30
ServerAliveCountMax 3
Host expose-web
HostName public.example.com
User ops
RemoteForward 8080 127.0.0.1:80
ExitOnForwardFailure yes
Host socks
HostName jump.example.com
User ops
DynamicForward 1080
ssh -N db-tunnel
ssh -N expose-web
ssh -N socks
5. 稳定性:autossh
SSH 隧道会因网络抖动断开,autossh 自动重连:
apt install -y autossh
autossh -M 0 -f -N \
-o "ServerAliveInterval 30" \
-o "ServerAliveCountMax 3" \
-o "ExitOnForwardFailure yes" \
-L 13306:10.0.0.21:3306 ops@jump
-M 0 关闭 autossh 自带监控端口,改用 SSH 的 keepalive(更可靠,避免端口冲突)。
用 systemd 托管:
# /etc/systemd/system/ssh-tunnel-db.service
[Unit]
Description=SSH tunnel to internal MySQL
After=network-online.target
Wants=network-online.target
[Service]
User=ops
ExecStart=/usr/bin/ssh -N -o ExitOnForwardFailure=yes -o ServerAliveInterval=30 \
-L 13306:10.0.0.21:3306 ops@jump.example.com
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
systemctl daemon-reload && systemctl enable --now ssh-tunnel-db
6. 验证与排查
# 1. 端口是否在监听
ss -lntp | grep 13306
# 2. 隧道是否通(不依赖应用)
nc -zv 127.0.0.1 13306
# 3. 看 SSH 详细日志
ssh -vvv -N -L 13306:10.0.0.21:3306 ops@jump
# 4. 服务端是否允许转发
grep -E 'AllowTcpForwarding|GatewayPorts|PermitOpen' /etc/ssh/sshd_config
# 5. 应用侧确认
mysql -h 127.0.0.1 -P 13306 -u app -p -e 'select 1'
常见错误:
| 报错 | 原因 |
|---|---|
bind: Address already in use |
本地端口被占用,换端口或 ss -lntp 找占用者 |
channel 2: open failed: administratively prohibited |
服务端 AllowTcpForwarding no |
connect failed: Connection refused |
远端目标地址/端口不对,或目标服务没起 |
remote port forwarding failed for listen port 8080 |
服务端端口被占或 GatewayPorts 限制 |
| 隧道建了但连不上 | 目标地址是相对"跳板机"的,不是相对你本机 |
7. 实战场景
7.1 连内网数据库(最常用)
ssh -f -N -L 13306:db-internal:3306 ops@jump
mysql -h 127.0.0.1 -P 13306 -u app -p appdb
7.2 访问内网 Web 后台
ssh -f -N -L 8080:admin.internal:80 ops@jump
# 浏览器打开 http://127.0.0.1:8080
7.3 让外部临时访问本地服务(内网穿透)
# 本地机器(无公网 IP)
ssh -R 9000:127.0.0.1:8080 ops@public-server -N
# 公网服务器上访问
curl http://127.0.0.1:9000
需要外部直接访问时,服务端配 GatewayPorts yes,然后访问 http://public-server:9000(注意安全,别长期暴露)。
7.4 经跳板机连第二层内网
ssh -J ops@jump1,ops@jump2 ops@target
# 或
ssh -o ProxyJump=jump1,jump2 target
7.5 浏览器走内网(SOCKS)
ssh -f -N -D 1080 ops@jump
# 浏览器/系统代理设为 SOCKS5 127.0.0.1:1080
curl --socks5-hostname 127.0.0.1:1080 http://wiki.internal
7.6 只允许特定转发(服务端限制)
# /etc/ssh/sshd_config
AllowTcpForwarding yes
PermitOpen 10.0.0.21:3306 10.0.0.22:6379
配合公钥选项限制:
permitopen="10.0.0.21:3306",no-pty,command="/bin/false" ssh-ed25519 AAAA... tunnel
8. 安全注意
- 隧道绕过网络边界:企业环境使用前必须获得授权,审计日志会记录。
-g与GatewayPorts yes会把端口暴露到网络,等于开了后门。-R长期驻留:公网服务器上形成持久入口,需配合防火墙白名单。AllowTcpForwarding no是常见加固项,启用隧道需评估。- 不要用隧道传敏感数据:SSH 加密是好的,但端点主机可能被入侵。
autossh密钥:使用专用、受限的密钥,别用个人主密钥。
9. 常见坑
-L的目标地址是相对跳板机:写127.0.0.1:3306指的是跳板机自己。- 不加
-N:会开一个交互 shell,脚本里容易挂住。 -f后无法看日志:排障时先不加-f,确认能通再后台化。GatewayPorts默认no:-R只绑 127.0.0.1,外部访问不到。ExitOnForwardFailure没开:端口占用时 SSH 建连成功但转发失败,脚本误以为成功。- 端口被旧隧道占用:
ss -lntp | grep <port>找到残留进程。 - 网络切换后隧道假死:TCP 连接还在但已不可用,配
ServerAliveInterval。 -D的 DNS 在本地解析:内网域名解析不了,用--socks5-hostname或在配置里加DynamicForward。- 多级跳转的
-L:-L绑在最终目标机上,-J只负责到达。 - 容器内建隧道:需要
NET_ADMIN?通常不需要,但端口映射要注意。 PermitOpen限制后报 prohibited:服务端策略不允许目标地址。- 长连接被中间设备掐断:
ClientAliveInterval与ServerAliveInterval都要配。
10. 速查表
# 本地转发
ssh -N -L 13306:db:3306 user@jump
ssh -f -N -L 13306:db:3306 user@jump
ssh -g -L 8080:web:80 user@jump -N # 允许他人连接
# 远程转发
ssh -N -R 8080:127.0.0.1:80 user@public
# 服务端需 GatewayPorts yes 才能外部访问
# 动态 SOCKS
ssh -f -N -D 1080 user@jump
curl --socks5-hostname 127.0.0.1:1080 http://internal
# 跳板
ssh -J jump1,jump2 user@target
# 稳定化
autossh -M 0 -f -N -o ServerAliveInterval=30 -L 13306:db:3306 user@jump
# 验证
ss -lntp | grep 13306
nc -zv 127.0.0.1 13306
ssh -vvv -N -L ...