用 SSH 反向端口转发,将本机代理给远程服务器使用

15 阅读6分钟

用 SSH 反向端口转发,将本机代理给远程服务器用

为什么学这个

最近我在远程服务器上做开发和装环境,遇到一个很实际的问题:服务器在使用 pip 安装 Python 包时,由于网络波动导致连接超时或下载中断,出现 ReadTimeoutError 或 ConnectionError 等异常,或者有些资源无法直接访问,但我本机已配置好代理,能够正常安装python依赖包。

我的目标很明确:

让远程服务器复用我本机的代理能力。

一开始我以为只要在服务器上“配置个代理”就行,后来才发现事情没那么简单。核心原因是:

  • 我本机的代理端口,比如 127.0.0.1:<本机代理端口>,默认只对本机自己可见
  • 远程服务器访问不到我电脑的 127.0.0.1
  • 所以必须借助一条SSH 隧道,把我本机的代理端口“带到”远程服务器上去

这次我学到的核心技术,就是:

SSH 反向端口转发(Remote Port Forwarding)


核心内容 / 步骤

1. 场景和目标

我的环境是:

  • 本机:Windows
  • 远程服务器:Windows
  • 本机代理端口:<本机代理端口>
  • 远程服务器地址:<远程服务器地址>
  • 远程登录用户:admin

我要实现的效果是:

在远程服务器上访问一个本地端口,比如 127.0.0.1:17897,实际流量通过 SSH 隧道回到我本机,再交给代理工具的 127.0.0.1:<本机代理端口> 处理。


2. 先确认本机代理端口可用

第一步不是急着连服务器,而是先确认我本机的 代理工具 端口本身是通的。

在本机 PowerShell 里测试:

curl.exe -x http://127.0.0.1:<本机代理端口> https://www.google.com

如果能返回网页内容,说明本机 代理工具 端口没问题。

这一步很重要。
因为如果本机代理本来就不通,后面的 SSH 转发再怎么配也没意义。


3. 用 SSH 建立反向端口转发

真正关键的命令是:

ssh -N -R 17897:127.0.0.1:<本机代理端口> admin@<远程服务器地址>

这条命令的含义可以拆成三部分理解:

  • ssh:连接远程服务器
  • -N:只建立隧道,不执行远程命令
  • -R 17897:127.0.0.1:<本机代理端口>:在远程服务器上开一个 17897 端口,把它转发到我本机127.0.0.1:<本机代理端口>

也就是说,执行成功后:

  • 远程服务器上的代理入口变成了:127.0.0.1:17897
  • 实际出口仍然是我本机 代理工具:127.0.0.1:<本机代理端口>

执行这条命令后,窗口会一直挂着不动,这是正常现象。
这个窗口不能关,因为它本身就是隧道。


4. 在远程服务器上设置代理

隧道打通后,还需要告诉远程服务器上的程序:以后访问网络走这个代理。

因为我的远程服务器是 Windows,而且终端有时是 CMD,有时是 PowerShell,所以这里要区分两种写法。

如果远程终端是 CMD
set http_proxy=http://127.0.0.1:17897
set https_proxy=http://127.0.0.1:17897
如果远程终端是 PowerShell
$env:http_proxy="http://127.0.0.1:17897"
$env:https_proxy="http://127.0.0.1:17897"

这一点是我这次踩坑最多的地方之一。
同样是 Windows,CMD 和 PowerShell 的环境变量写法并不一样。


5. 测试代理是否真的生效

在远程终端里测试:

curl.exe -I https://www.google.com

如果看到类似输出:

HTTP/1.1 200 Connection established
HTTP/1.1 200 OK

基本就能判断代理已经生效了。

尤其是这行:

HTTP/1.1 200 Connection established

它说明请求是先通过代理建立了 HTTPS 隧道,再去访问目标网站。

这比单纯返回网页内容更能证明“确实走了代理”。


6. 在 VS Code Remote SSH 里自动带上这条隧道

手工执行 ssh -N -R ... 能跑通之后,我又继续做了一步优化:
VS Code 通过 SSH 连接服务器时,自动带上这个反向转发

做法是在本机的 SSH 配置文件里加入:

Host myserver
    HostName <远程服务器地址>
    User admin
    RemoteForward 17897 127.0.0.1:<本机代理端口>

配置文件一般在:

C:\Users\你的用户名.ssh\config

然后在 VS Code 里连接 myserver,这样 VS Code 发起 SSH 连接时,会自动建立:

远程 127.0.0.1:17897 -> 本机 127.0.0.1:<本机代理端口>

之后我只需要在 VS Code 的远程终端里设置:

$env:http_proxy="http://127.0.0.1:17897"
$env:https_proxy="http://127.0.0.1:17897"

就能直接在远程开发环境里用代理了。


7. 让 VS Code 终端自动带代理变量

为了避免每次都手动输入环境变量,我还可以在 VS Code 远程窗口里配置 terminal 环境变量,或者修改远程 PowerShell 的 $PROFILE

例如在 VS Code Remote Settings JSON 里加:

{
  "terminal.integrated.env.windows": {
    "http_proxy": "http://127.0.0.1:17897",
    "https_proxy": "http://127.0.0.1:17897"
  }
}

这样每次新开远程终端,代理变量都会自动带上。


遇到的问题与解决方法

问题 1:CMD 和 PowerShell 语法不同

在远程服务器上,我有时进入的是 CMD,有时进入的是 PowerShell。

  • CMD 写法:

    set http_proxy=http://127.0.0.1:17897
    
  • PowerShell 写法:

    $env:http_proxy="http://127.0.0.1:17897"
    

如果语法用错,通常不会得到想要的效果。


问题 2:PowerShell 里的 curl 不是我以为的那个 curl

在 PowerShell 里执行:

curl -I https://www.google.com

结果触发的是 Invoke-WebRequest,还提示输入 Uri

解决方法有两个:

显式调用真正的 curl
curl.exe -I https://www.google.com
或者直接用 PowerShell 命令
(Invoke-WebRequest -Uri https://www.google.com).StatusCode

这个坑很典型,也很容易误判成“代理没通”。


问题 3:VS Code Remote SSH 连上了,不代表代理隧道也有了

这是一个理解上的关键点。

  • VS Code Remote SSH 负责的是“我能不能远程开发”
  • SSH -R / RemoteForward 负责的是“远程服务器能不能借用我本机代理”

这两者不是一回事。

如果只连了 VS Code,但没带 RemoteForward,远程端不会自动出现 127.0.0.1:17897 这个入口。

解决方法有两个:

  1. 保留一条单独的 ssh -N -R ... 隧道连接
  2. ~/.ssh/config 里为该主机加 RemoteForward

我最后采用的是第二种,体验更好。


问题 4:代理工具 的“流量隧道管理”看起来很像,但不是一个东西

我中间还研究了 代理工具 自带的“流量隧道管理”功能,以为它和 SSH 反向转发作用相同。

后来搞清楚了:

  • 代理工具 流量隧道:主要是在本机开一个端口做转发
  • SSH -R:是在远程服务器上开一个端口,把流量带回本机

所以它们不是替代关系。
对“让远程服务器借用我本机代理”这个需求来说,核心还是 SSH RemoteForward


收获与总结

这次实践最大的收获,不是单纯“把代理配通了”,而是把几个容易混在一起的概念真正理顺了:

1. 本机代理端口默认只对本机可见

像 代理工具 的 127.0.0.1:<本机代理端口>,远程服务器默认访问不到。
这也是为什么必须借助 SSH 隧道。

2. SSH 反向端口转发非常适合“把本机能力借给远程机器”

我现在对这条命令的理解已经很清楚了:

ssh -N -R 17897:127.0.0.1:<本机代理端口> admin@<远程服务器地址>

本质就是:

在远程开入口,在本机做出口。

3. VS Code Remote SSH 和代理隧道是两层东西

VS Code 负责远程开发连接,RemoteForward 负责远程代理能力。
理解这一点之后,很多“为什么我明明连上服务器了,代理却没用”的问题就很容易解释了。

4. 最终可用方案

我最终沉淀下来的实用方案是:

  • 本机 代理工具 正常运行,端口如 <本机代理端口>

  • SSH config 中为远程主机配置:

    Host myserver
        HostName <远程服务器地址>
        User admin
        RemoteForward 17897 127.0.0.1:<本机代理端口>
    
  • VS Code 通过 myserver 连接

  • 远程终端中配置(Windows系统):

    $env:http_proxy="http://127.0.0.1:17897"
    $env:https_proxy="http://127.0.0.1:17897"
    
  • 远程终端中配置(Linux系统):

    export http_proxy="http://127.0.0.1:17897"
    export https_proxy="http://127.0.0.1:17897"