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 端口 2222
  • ops@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 上有可用代理时,服务器就能通过隧道使用它。

-L 的方向相反

更常见的正向端口转发是:

ssh -L 本地端口:目标地址:目标端口 user@server

它解决的是“当前电脑如何访问远端内网服务”。

比如把服务器内网里的 MySQL 映射到本机,这很常见。

反向端口转发的方向相反:

ssh -R 远端端口:本地地址:本地端口 user@server

它解决的是“远端服务器如何访问客户端能访问的服务”。

因此,-R 可以把客户端能访问的服务临时提供给远端服务器,适合排障或短期访问。

最小可用配置

假设你的客户端已经有一个本地代理:

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

加上 ExitOnForwardFailure 后,隧道创建失败时 SSH 会直接退出:

ssh -N -p 2222 \
    -o ExitOnForwardFailure=yes \
    -R 18080:127.0.0.1:10808 \
    ops@203.0.113.10

再加 keepalive,让 SSH 更快发现断线:

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,这样域名解析也会交给代理端完成。否则服务器本身 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

先用 curl 确认最小链路,再配置具体工具。这样更容易定位问题。

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 连接访问外部服务。

正向转发让客户端进入远端网络,反向转发则让远端服务器使用客户端能访问的服务。

遇到临时网络问题时,可以先建立反向隧道验证请求链路,再决定是否需要改造防火墙、NAT 或网关。