SSH 反向隧道:让服务器通过客户端代理访问互联网
很多服务器不能直接访问互联网。常见原因不是机器坏了,而是它处在内网、受出口策略限制,或者云上环境没有配置公网出口。
这时很多人的第一反应是:给服务器装代理、改防火墙、开公网出口、加 NAT 网关。
这些办法都可以,但它们改变的是服务器所在网络的出口结构。权限不够时,往往做不了;临时排障时,也太重。
还有一种更轻的办法:如果你的客户端机器已经能访问互联网,并且本地已经有一个代理端口,比如 127.0.0.1:10808,就可以用 SSH 反向端口转发,把这个代理能力挂到服务器上。服务器访问自己的 127.0.0.1:18080,实际流量会穿过 SSH 隧道,转到客户端的 127.0.0.1:10808。
先假设有这样一组配置:
服务器地址:203.0.113.10
服务器 SSH 端口:2222
服务器 SSH 用户:ops
客户端本地代理地址:127.0.0.1
客户端本地代理端口:10808
服务器侧代理入口:127.0.0.1:18080
基于这组配置,核心命令如下:
ssh -p 2222 \
-R 18080:127.0.0.1:10808 \
ops@203.0.113.10
这条命令的重点在 -R。
这条命令真正做了什么
先看完整结构:
ssh -p 2222 \
-R 18080:127.0.0.1:10808 \
ops@203.0.113.10
含义是:
-p 2222:连接服务器203.0.113.10的 SSH 端口2222ops@203.0.113.10:以用户ops登录服务器-R 18080:127.0.0.1:10808:在服务器侧监听18080,并把连接转发到客户端侧的127.0.0.1:10808
连接建立后,链路变成这样:
服务器上的程序
-> 127.0.0.1:18080
-> SSH 反向隧道
-> 客户端 127.0.0.1:10808
-> 客户端本地代理
-> 互联网
注意,这不是让客户端访问服务器的端口,而是让服务器借用客户端的端口。
更准确地说,-R 的格式是:
-R [远端监听地址:]远端端口:本地地址:本地端口
所以:
-R 18080:127.0.0.1:10808
可以理解成:
服务器 127.0.0.1:18080 -> 客户端 127.0.0.1:10808
SSH 只负责转发 TCP 连接,并不关心里面跑的是 HTTP 代理、SOCKS5 代理,还是别的协议。只要客户端的 127.0.0.1:10808 上确实有一个可用代理,服务器就能通过这个隧道使用它。
为什么这个方式容易被忽略
大部分人熟悉的是正向端口转发:
ssh -L 本地端口:目标地址:目标端口 user@server
它解决的是“我的电脑怎么访问远端内网服务”。
比如把服务器内网里的 MySQL 映射到本机,这很常见。
反向端口转发刚好反过来:
ssh -R 远端端口:本地地址:本地端口 user@server
它解决的是“远端服务器怎么访问我本机能访问的服务”。
这个方向一换,价值就变了。它不再只是调试端口,而是可以临时改变服务器的出口路径。
最小可用配置
假设你的客户端已经有一个本地代理:
127.0.0.1:10808
这个代理可能是 SOCKS5,也可能是 HTTP。先确认它在客户端可用。
如果是 SOCKS5 代理,可以在客户端测试:
curl --socks5-hostname 127.0.0.1:10808 https://ifconfig.me
如果是 HTTP 代理,可以测试:
curl -x http://127.0.0.1:10808 https://ifconfig.me
确认客户端代理可用后,再从客户端连接服务器:
ssh -p 2222 \
-R 18080:127.0.0.1:10808 \
ops@203.0.113.10
登录服务器后,在服务器上测试:
curl --socks5-hostname 127.0.0.1:18080 https://ifconfig.me
如果你的本地代理是 HTTP 代理,就用:
curl -x http://127.0.0.1:18080 https://ifconfig.me
如果能返回公网 IP,说明服务器已经通过客户端代理访问互联网。
更适合长期挂着的写法
如果只是临时验证,前面的命令就够了。
如果只是建立隧道,不需要登录 shell,可以加上 -N:
ssh -N -p 2222 \
-R 18080:127.0.0.1:10808 \
ops@203.0.113.10
为了避免隧道建立失败但 SSH 仍然连上,可以加:
ssh -N -p 2222 \
-o ExitOnForwardFailure=yes \
-R 18080:127.0.0.1:10808 \
ops@203.0.113.10
为了让连接更容易发现断线,可以加 keepalive:
ssh -N -p 2222 \
-o ExitOnForwardFailure=yes \
-o ServerAliveInterval=30 \
-o ServerAliveCountMax=3 \
-R 18080:127.0.0.1:10808 \
ops@203.0.113.10
如果要长期运行,可以再配合 autossh 或 systemd user service。这里先不展开。核心不是守护进程,而是先确认这条链路本身成立。
让命令行工具使用这个代理
隧道建立后,服务器上会出现一个本地代理入口:
127.0.0.1:18080
如果客户端的 10808 是 SOCKS5 代理,服务器上的命令可以通过服务器侧的 18080 入口走代理:
curl --socks5-hostname 127.0.0.1:18080 https://github.com
也可以临时设置环境变量:
export ALL_PROXY=socks5h://127.0.0.1:18080
export HTTP_PROXY=socks5h://127.0.0.1:18080
export HTTPS_PROXY=socks5h://127.0.0.1:18080
socks5h 的 h 很重要。它表示域名解析也交给代理端完成。否则服务器如果本身 DNS 不通,仍然会失败。
如果客户端的 10808 是 HTTP 代理,则在服务器上使用:
export HTTP_PROXY=http://127.0.0.1:18080
export HTTPS_PROXY=http://127.0.0.1:18080
Git 也可以单独配置:
git config --global http.proxy socks5h://127.0.0.1:18080
git config --global https.proxy socks5h://127.0.0.1:18080
用完后取消:
git config --global --unset http.proxy
git config --global --unset https.proxy
常见问题
服务器上访问 127.0.0.1:18080 失败
先确认 SSH 命令还在运行。反向端口转发依赖这条 SSH 连接,连接断了,服务器上的 18080 也就没了。
再检查服务器上端口是否存在:
ss -lntp | grep 18080
如果端口没有监听,可能是远端 SSH 服务不允许 TCP 转发。服务器的 sshd_config 里需要允许:
AllowTcpForwarding yes
修改后需要重载或重启 sshd。
远端端口被占用
如果服务器上已经有程序占用了 18080,可以换一个远端端口:
ssh -N -p 2222 \
-R 28080:127.0.0.1:10808 \
ops@203.0.113.10
这时服务器使用:
curl --socks5-hostname 127.0.0.1:28080 https://ifconfig.me
链路变成:
服务器 127.0.0.1:28080 -> 客户端 127.0.0.1:10808
curl 可以,git 或 npm 不行
这通常不是隧道问题,而是工具没有使用代理,或者代理协议写错了。
先用 curl 确认最小链路:
curl -v --socks5-hostname 127.0.0.1:18080 https://github.com
再分别配置具体工具。不要一开始就从复杂工具排查。
DNS 仍然失败
如果使用 SOCKS5,优先使用 socks5h:// 或 --socks5-hostname。这会把域名解析交给代理侧。
如果写成 socks5://,很多工具会先在服务器本地解析域名。服务器 DNS 不通时,代理还没开始工作,请求已经失败。
其他机器能不能访问服务器上的 18080
默认情况下,OpenSSH 的反向端口通常只监听服务器的 loopback 地址,也就是服务器自己访问:
127.0.0.1:18080
这正是推荐方式。它只把代理暴露给这台服务器自己,风险较小。
如果希望服务器所在网络的其他机器也访问这个反向代理,需要显式绑定地址,并且服务器 SSH 配置还要允许 GatewayPorts。例如:
ssh -N -p 2222 \
-R 0.0.0.0:18080:127.0.0.1:10808 \
ops@203.0.113.10
这会扩大暴露面。除非你非常清楚访问控制边界,否则不建议这么做。
安全边界
这类隧道很方便,但它本质上是在让服务器借用客户端的网络出口。
需要注意几点:
- 不要把远端监听地址开放到
0.0.0.0,除非你已经做好访问控制 - 不要在多人共享服务器上随便暴露代理端口
- 不要把这个方式当成长期网络架构,长期使用应该有正式出口、审计和权限边界
- 不要忽略客户端代理的访问规则,服务器发出的请求最终会从客户端代理出口出去
它最适合的场景是临时排障、临时下载依赖、访问受限 API、在无公网出口的服务器上做一次可控操作。
这件事的关键变化
通常我们以为服务器访问互联网,出口一定在服务器所在网络里。
SSH 反向隧道改变的是这个控制点:服务器不直接拥有出口,但它可以通过一条已经建立的 SSH 连接,借用客户端的出口能力。
这不是代理软件的新功能,而是 SSH 端口转发方向带来的系统变化。
正向转发解决“客户端进入远端网络”。
反向转发解决“远端服务器借用客户端能力”。
只要理解这个方向,很多临时网络问题就不用从防火墙、NAT、网关开始改。先挂一条 SSH 反向隧道,确认请求链路能不能跑通,再决定是否需要正式改网络架构。
