SSH 隧道 —— 不装软件打通内网

运维工具手册 12-隧道与高可用 5.9 KB 原文

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. 安全注意

  1. 隧道绕过网络边界:企业环境使用前必须获得授权,审计日志会记录。
  2. -g 与 GatewayPorts yes 会把端口暴露到网络,等于开了后门。
  3. -R 长期驻留:公网服务器上形成持久入口,需配合防火墙白名单。
  4. AllowTcpForwarding no 是常见加固项,启用隧道需评估。
  5. 不要用隧道传敏感数据:SSH 加密是好的,但端点主机可能被入侵。
  6. autossh 密钥:使用专用、受限的密钥,别用个人主密钥。

9. 常见坑

  1. -L 的目标地址是相对跳板机:写 127.0.0.1:3306 指的是跳板机自己。
  2. 不加 -N:会开一个交互 shell,脚本里容易挂住。
  3. -f 后无法看日志:排障时先不加 -f,确认能通再后台化。
  4. GatewayPorts 默认 no:-R 只绑 127.0.0.1,外部访问不到。
  5. ExitOnForwardFailure 没开:端口占用时 SSH 建连成功但转发失败,脚本误以为成功。
  6. 端口被旧隧道占用:ss -lntp | grep <port> 找到残留进程。
  7. 网络切换后隧道假死:TCP 连接还在但已不可用,配 ServerAliveInterval。
  8. -D 的 DNS 在本地解析:内网域名解析不了,用 --socks5-hostname 或在配置里加 DynamicForward。
  9. 多级跳转的 -L:-L 绑在最终目标机上,-J 只负责到达。
  10. 容器内建隧道:需要 NET_ADMIN?通常不需要,但端口映射要注意。
  11. PermitOpen 限制后报 prohibited:服务端策略不允许目标地址。
  12. 长连接被中间设备掐断: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 ...
SSH 隧道 —— 不装软件打通内网 - 知识库 - 起航社区