SSH 隧道还能干什么
先建立一张“能力地图”,后面所有用法都是这张图上的组合:
| 能力 | 命令 | 本质 |
|---|---|---|
| 定向端口转发 | -L / -R |
一对一 TCP 搬运 |
| 动态代理 | -D |
一对任意 TCP(本地 SOCKS5) |
| netcat 模式 | -W host:port |
把隧道当 nc 用,输出到 stdout |
| Unix 套接字转发 | -L sock:sock / -R |
把 socket 文件当端口 |
| 认证转发 | -A(ForwardAgent) |
ssh-agent 的 socket 被转发 |
| 图形转发 | -X / -Y |
X11 协议走隧道 |
| 链路拼接 | -J / ProxyCommand |
隧道套隧道 |
下面按“实际想解决的问题”来讲。
一、代理与统一出口(-D 的战场)
-D 开的是本地 SOCKS5,目标不写死,所以它的用途比 -L 更广:
1. 让工具链走远端出口
ssh -N -D 1080 me@jump
curl --socks5-hostname 127.0.0.1:1080 https://ifconfig.me
all_proxy=socks5h://127.0.0.1:1080 npm install
git -c http.proxy=socks5h://127.0.0.1:1080 clone https://github.com/x/y
注意用 socks5h(--socks5-hostname),让 DNS 也在远端解析,否则域名在你本地解析、流量走远端,既可能解析失败也泄露意图。
2. 固定出口 IP 过白名单
第三方 API 只放行某个固定公网 IP,而你本地是动态 IP:
ssh -N -D 1080 me@fixed-ip-vps
# 所有请求从 vps 的 IP 出去
比买代理、配 NAT 网关都轻。
3. 浏览器分流
Firefox 直接支持 SOCKS5 代理(配合 FoxyProxy 做规则分流),Chrome 需要 --proxy-server 或系统代理。可以只让某几个域名走隧道。
4. 反向 SOCKS:把“你的机器”变成出口
-D 只能在本机开 SOCKS。想在远端开 SOCKS,就把它和 -R 拼起来:
ssh -N -D 1080 -R 1080:localhost:1080 me@vps
# 现在 vps 上的 127.0.0.1:1080 是一个 SOCKS,出口是你的机器
这是“让你身边的网络位置被远端使用”的常用手法。
二、给裸奔的协议套一层加密
这是被严重低估的用途。很多服务协议本身明文、无认证或弱认证:
| 协议 | 明文风险 | 隧道做法 |
|---|---|---|
| Redis | 无认证/明文,暴露即被挖矿 | ssh -L 6379:127.0.0.1:6379 me@cache |
| MySQL/PG | 认证可降级、流量可嗅探 | ssh -L 15432:127.0.0.1:5432 me@db |
| VNC / RDP | 弱加密甚至明文 | ssh -L 5901:127.0.0.1:5901 me@desktop |
| LDAP | 明文绑定密码 | ssh -L 389:dc:389 me@jump |
| Elasticsearch / Prometheus | 常无认证 | ssh -L 9200:es:9200 me@jump |
| 云 metadata | 169.254.169.254 只在内网可达 |
ssh -L 8888:169.254.169.254:80 me@ec2 |
注意一个常被忽略的事实:SSH 隧道只加密“你到跳板机”这一段。跳板机到目标那一跳仍是明文——如果那段网络也不可信,隧道救不了你。
三、访问只监听 127.0.0.1 的 Web 控制台
这是日常最高频的场景,几乎每个数据/ML/运维工具都属于这一类:
ssh -N -L 8888:localhost:8888 me@gpu-box # Jupyter
ssh -N -L 6006:localhost:6006 me@gpu-box # TensorBoard
ssh -N -L 5000:localhost:5000 me@ml-server # MLflow
ssh -N -L 8001:localhost:8001 me@k8s-master # k8s Dashboard
ssh -N -L 3000:localhost:3000 me@monitor # Grafana
ssh -N -L 9090:localhost:9090 me@monitor # Prometheus
ssh -N -L 9000:localhost:9000 me@ci # Portainer / ArgoCD
一次可以开多个:
ssh -N -L 8888:localhost:8888 -L 6006:localhost:6006 -L 9090:localhost:9090 me@gpu-box
Kubernetes 里的 kubectl port-forward 本质就是这类东西(走 API Server 隧道而不是 SSH):
kubectl port-forward -n monitoring svc/grafana 3000:80
四、跳板与多跳(隧道套隧道)
1. ProxyJump(推荐的现代写法)
ssh -J me@jump me@target # 相当于先连 jump,再从 jump 连 target
ssh -J me@a,me@b,me@c me@target # 多级
它在 ~/.ssh/config 里长这样:
Host target
HostName 10.0.3.21
User me
ProxyJump jump.example.com
2. -W netcat 模式
ssh -W 10.0.3.21:5432 me@jump
这会把一条裸 TCP 流打到 stdout,等于“用 SSH 认证的 nc”。它的价值在于可以塞进任何期望“ProxyCommand”的地方:
ssh -o "ProxyCommand=ssh -W %h:%p me@jump" me@target
3. 让别的工具也走跳板
很多工具不认识 SSH,但它们认识 ProxyCommand 或者能借 SSH 的壳:
rsync -av -e "ssh -J me@jump" ./data/ me@target:/data/
scp -o ProxyJump=me@jump ./a.txt me@target:/tmp/
git -c core.sshCommand="ssh -J me@jump" clone me@target:/repo
Git 的 ssh://、Docker 的 DOCKER_HOST=ssh://me@host、Ansible 的 ansible_ssh_common_args 都属于同一思路:把 SSH 当通用传输层。
4. 借用第三方网络位置
ssh -N -L 3306:mysql.prod:3306 me@jump # 只要能连到,目标不必在跳板机上
这在“我本机被墙/被限,但跳板机通”时特别有用。
五、内网穿透与反向暴露(-R 的战场)
1. 常驻反向隧道(自建 ngrok)
autossh -M 0 -N -o ServerAliveInterval=30 -o ExitOnForwardFailure=yes \
-R 0.0.0.0:9000:localhost:3000 me@vps
配 systemd 就能开机自启、断线自愈:
[Unit]
Description=Reverse tunnel to vps
After=network-online.target
[Service]
User=me
ExecStart=/usr/bin/autossh -M 0 -N -o ServerAliveInterval=30 -o ExitOnForwardFailure=yes -R 0.0.0.0:9000:localhost:3000 me@vps
Restart=always
[Install]
WantedBy=multi-user.target
2. 本地调试外部 Webhook
Stripe / GitHub / 微信 / 支付的回调没法打到你的笔记本,用 -R 造一个公网回调地址:
ssh -N -R 0.0.0.0:8080:localhost:3000 me@vps
# 回调地址填 http://vps:8080/webhook
3. 远程协助:反向进入客户内网
在客户机器上执行(他主动连出,你不需要任何入站权限):
ssh -N -R 2222:localhost:22 support@my-vps
# 你在 vps 上:ssh -p 2222 user@127.0.0.1 即进客户机器
4. IoT / 边缘设备维护
树莓派、工控机、门店路由器在内网,没有公网 IP,靠反向隧道维持一条可维护通道。
5. 临时协作
ssh -N -R 8080:localhost:8080 demo@demo-server # 把 demo-server:8080 发给同事
六、文件与挂载
scp/sftp 本身就跑在 SSH 上,不需要额外隧道;但隧道的链路复用能力很有用:
1. 把远端目录挂到本地
sshfs me@host:/data /mnt/remote -o reconnect,ServerAliveInterval=15
fusermount -u /mnt/remote
配合 ProxyJump 也能挂隔着跳板机的目录。
2. 连接复用,避免反复握手
Host *
ControlMaster auto
ControlPath ~/.ssh/cm/%r@%h:%p
ControlPersist 10m
之后 ssh、scp、rsync、git 全部复用同一条底层连接,速度提升非常明显(尤其是有 2FA 或高延迟链路时)。
3. 原地流式搬运(不落盘中转)
ssh me@a "tar czf - /data" | ssh me@b "tar xzf - -C /"
这是“用 SSH 当传输管道”的经典写法,不经过本地磁盘。
七、认证与图形转发
1. -A Agent 转发
把本机的 ssh-agent socket 转发到远端,这样在跳板机上继续 ssh/git 时不用把私钥拷过去:
ssh -A me@jump
jump$ ssh-add -l # 能看到你的 key
jump$ git clone git@github.com:x/y
⚠️ 风险很大:跳板机上拥有 root 的人,在你连接期间可以用你的 agent 冒充你(SSH_AUTH_SOCK 指向那个 socket 就行)。缓解手段:
- 优先用
ProxyJump代替-A(私钥永远不出本机); - 必须用时加
ssh-add -c(每次使用需确认); - 用
ssh-add -t 300给 key 设过期时间; - 只在可信主机上
-A,config 里显式按 Host 开启而不是Host *。
2. -X / -Y X11 转发
ssh -X me@host # 不可信模式,有限制
ssh -Y me@host # 可信模式,等价于允许远端操纵你的 X server(有风险)
用来看远端 GUI 程序(MATLAB、Wireshark、virt-manager)。要高帧率或复杂图形,改用 VNC/XRDP 走 -L 通常更好。
3. Unix socket 转发(OpenSSH 6.7+)
# 把远端的 docker.sock 拉到本地(危险,等于给远端 root)
ssh -L /tmp/docker.sock:/var/run/docker.sock me@host
DOCKER_HOST=unix:///tmp/docker.sock docker ps
# 转发 gpg-agent / ssh-agent socket
ssh -R /tmp/remote-gpg.sock:/run/user/1000/gnupg/S.gpg-agent me@host
# 本地 socket → 远端端口
ssh -L /tmp/x.sock:localhost:8080 me@host
如果 socket 已存在导致绑定失败,服务端加 StreamLocalBindUnlink yes。
八、把 SSH 当通用传输层
1. UDP over SSH(SSH 不支持 UDP,只能手工桥)
SSH 隧道只搬 TCP。要过 UDP,就在两端各放一个转换器:
# 远端(跳板机上)把 TCP 5353 转成到 8.8.8.8 的 UDP
jump$ socat TCP-LISTEN:5353,reuseaddr,fork UDP:8.8.8.8:53
# 本地建隧道
ssh -N -L 5353:localhost:5353 me@jump
# 本地用 TCP 去问
dig +tcp @127.0.0.1 -p 5353 example.com
2. sshuttle:基于 SSH 的“穷人 VPN”
sshuttle -r me@jump 0.0.0.0/0 --dns
它不需要在远端装东西(只需 python3),本地用 iptables/pf 拦截流量,把 TCP + DNS 全部塞进一条 SSH 连接。适合“我要让整个系统像在远端网络里一样”,且不想配 VPN 的场景。缺点:只支持 TCP + DNS,UDP 和 ICMP 不过。
3. 端口转发做串口/设备桥
ssh -L 5000:/dev/ttyUSB0 me@host # 不直接支持设备,需 socat 帮忙
# 正确做法:
jump$ socat TCP-LISTEN:5000,reuseaddr,fork /dev/ttyUSB0,raw,echo=0
ssh -N -L 5000:localhost:5000 me@jump
九、在受限网络里让 SSH 活着
出站 22 被封,或被 HTTP 代理卡住:
# sshd 监听 443(最简单,但和企业 HTTPS 冲突)
ssh -p 443 me@host
# 走 HTTP CONNECT 代理
ssh -o "ProxyCommand=proxytunnel -p proxy:8080 -d %h:%p" me@host
ssh -o "ProxyCommand=nc -X connect -x proxy:8080 %h %p" me@host
# 套 TLS,伪装成普通 HTTPS(服务端用 stunnel 转回 22)
ssh -o "ProxyCommand=openssl s_client -quiet -connect host:443 -servername host" me@host
对比一下:frp、ngrok、Cloudflare Tunnel、Tailscale 是这些手法的产品化封装,优势在 NAT 穿透、多路复用、可观测性;SSH 的优势是零额外依赖、零成本、随时可用。
十、CI/CD 与容器场景
1. CI runner 反向暴露给外部
一些云服务需要回调 CI 环境时,用 -R 开个临时口。
2. 本地连生产集群里的服务
ssh -N -L 8080:argocd-server.argocd.svc:443 me@k8s-node
3. Docker 远程
DOCKER_HOST=ssh://me@host docker ps
这是 Docker 自带的 SSH 传输(内部就是 ssh -W 那套思路),比开 2375 明文端口安全得多。
十一、反过来:怎么管住这些能力
正因为 SSH 隧道太好用,它也是数据外泄、内网横向、绕过审计的经典通道。所以服务端要知道怎么关:
# /etc/ssh/sshd_config
AllowTcpForwarding no # 全关
AllowTcpForwarding local # 只允许 -L(禁止 -R)
PermitOpen db.internal:5432 # 只允许转发到指定目标
PermitListen 127.0.0.1:9000 # 限制 -R 可监听的地址
GatewayPorts no # 禁止 -R 对外开放
X11Forwarding no
AllowAgentForwarding no
还可以按用户/组精细化:
Match Group contractors
AllowTcpForwarding no
PermitOpen none
监控侧看什么:sshd 日志里的 Local connections to ... forwarded to / Remote connections from ... forwarded to,以及异常的长时间空闲连接、大流量单向连接。
十二、必须知道的局限
| 局限 | 说明 | 应对 |
|---|---|---|
| 只搬 TCP | UDP/ICMP 过不去 | socat 桥接、sshuttle、或改用 WireGuard |
| 单流受限于一条 TCP | 高延迟链路上吞吐明显低于裸连,多流并发容易互相拖累 | 开 ControlMaster 复用、-C、换 aes128-gcm、或拆多条隧道 |
| 断线即断隧道 | 无自动重连 | ServerAliveInterval + autossh/systemd |
| 最后一跳不加密 | 跳板机到目标那段仍是明文 | 目标侧自己上 TLS |
| DNS 泄漏 | 用 socks5 而非 socks5h 时域名在本地解析 |
用 socks5h 或远端解析 |
-R 静默降级 |
GatewayPorts no 时对外绑定请求被悄悄改成 127.0.0.1 |
服务端显式配 clientspecified,并用 ss -lntp 验证 |
| 绑定失败不报错 | 端口被占用时 SSH 仍然登录成功 | ExitOnForwardFailure yes |
十三、速查表
| 我想…… | 用 |
|---|---|
| 访问内网数据库/服务 | -L |
| 访问只监听 127.0.0.1 的控制台 | -L(可用 -N -f) |
| 让工具走远端出口 / 固定出口 IP | -D + socks5h |
| 把本地服务暴露到公网 | -R + GatewayPorts clientspecified |
| 给裸奔协议加密 | -L |
| 穿过堡垒机连更深一层 | -J / ProxyCommand ssh -W |
| 整个系统当在远端网络里 | sshuttle |
| 挂载远端目录 | sshfs(+ ProxyJump) |
| 不拷私钥却能在远端用 git | -A 或(更好)ProxyJump |
| 看远端 GUI 程序 | -X / -Y |
| 转发 socket 文件(docker/gpg/agent) | -L sock:sock / -R |
| 穿透 UDP | socat 两端桥接 |
| 出站 22 被封 | 443 端口 / proxytunnel / stunnel |
| 把 SSH 连接复用给 scp/rsync/git | ControlMaster + ControlPersist |
一句话总结:SSH 隧道真正的价值不是“转发端口”,而是它提供了一个零依赖、带认证、加密的通用 TCP 传输层。凡是“我这里不通、那边通”的网络问题,几乎都能用它拼出一个解。