如何使用远程服务器作为本机的代理
这两年代理软件管得比较严,但 ChatGPT 对科研和学习又不可或缺。通常的解决办法是找更稳定的代理节点,也就是俗称的“机场”。不过,如果你手上有一台本身已经能访问外网的远程服务器(别管是怎么来的),也可以换一种思路:让本机通过 SSH 连接这台服务器,再把它作为本机的网络出口。这样一来,本机访问网站时,流量会先走到服务器,再由服务器访问目标网站。
如何使用远程服务器作为本机的代理
本文介绍的方法依赖一个前提:远程服务器本身已经可以访问目标网站,而本机可以通过 SSH 登录这台服务器。
这种服务器在企业、学校或实验室环境中并不少见,我就不详细讲了。
如果服务器已经配置好了外网访问能力,那么我们就可以利用 SSH 动态端口转发,在本机创建一个 SOCKS 代理入口,让本机流量经由这台服务器发出。
直观地说:
本机 |
因此,只要服务器能访问某个网站,本机也可以通过这个隧道,借由服务器访问它。
使用方法
假设远程服务器信息如下:
服务器 IP: 1.2.3.4 |
1. 在本机创建 SOCKS5 代理入口
在本机运行:
ssh -N -D 127.0.0.1:1080 -p 2010 [email protected] |
这条命令会在本机创建一个 SOCKS5 代理入口:
127.0.0.1:1080 |
只要这个 SSH 连接保持不断,本机就可以通过 127.0.0.1:1080 把网络流量转发到远程服务器,再由服务器访问目标网站。
如果服务器已经写入 ~/.ssh/config:
Host my-server |
那么命令可以简化为:
ssh -N -D 127.0.0.1:1080 my-server |
2. 让 GUI 应用使用系统代理
对于 Chrome、Safari 等 GUI 应用,通常可以直接使用 macOS 系统代理。
打开 macOS 设置:
System Settings |
开启:
SOCKS Proxy |
填写:
Server: 127.0.0.1 |
保存后,Chrome、Safari 等支持系统代理的应用通常就会使用这个代理。此时访问 ChatGPT、YouTube 等网站时,流量路径大致是:
Chrome |
目标网站看到的出口 IP 是远程服务器的 IP,而不是本机的 IP。
3. 让终端程序使用代理
需要注意的是,macOS 系统代理通常不会自动作用于终端程序。例如:
codexbrewgit clonecurlpipnpm
这些程序更常见的代理配置方式是环境变量。
如果要让 Codex CLI 使用该 SOCKS 代理,可以这样启动:
ALL_PROXY="socks5h://127.0.0.1:1080" \ |
例如,如果要启动 Codex App 并让它使用代理:
ALL_PROXY="socks5h://127.0.0.1:1080" \ |
这里推荐使用 socks5h://,而不是 socks5://。区别是:socks5h 会把域名解析也交给代理侧处理,避免本机 DNS 解析失败或泄漏。
对于其他终端程序,也可以用同样的方式。例如:
ALL_PROXY="socks5h://127.0.0.1:1080" git clone <repo-url> |
或者:
ALL_PROXY="socks5h://127.0.0.1:1080" brew update |
4. 写成脚本
如果经常使用,可以把启动命令和环境变量写成脚本。例如可以写一个 codex-proxy,每次自动带代理启动 Codex。
这类脚本很简单,不必手写很久。把当前代理端口和目标程序告诉 AI,就可以直接生成。
不建议把代理环境变量无脑写进全局 ~/.zshrc,因为这样会影响所有终端程序,包括 ssh、git、conda、pip 等。更推荐为特定程序写单独的 wrapper。
5. 和 Clash 等规则代理工具集成
直接把系统代理设置为 127.0.0.1:1080 有一个明显限制:它是粗粒度的全局代理。凡是读取系统 SOCKS 代理的应用,都会把流量交给这台远程服务器。
这在只想访问 ChatGPT、YouTube 等站点时足够。但如果希望:
- ChatGPT 走远程服务器;
- bilibili、网易云音乐、国内 CDN 直连;
- Reddit 或 YouTube 继续走商业代理节点;
- 本机回环地址、实验室内网、VPN 内网保持直连;
那么单纯的 ssh -D 就不够了。ssh -D 只提供一个 SOCKS5 出口,不负责规则分流。此时更合理的结构是让 Clash、Clash Verge、Mihomo 等规则代理工具负责系统代理和 TUN,把 SSH SOCKS 入口作为其中一个上游节点。
推荐链路是:
本机应用 |
而不是:
本机应用 |
Clash 配置中可以把 SSH SOCKS 入口写成一个普通 SOCKS5 节点:
proxies: |
然后根据自己的规则或代理组选择是否使用这个节点。这样可以保留原有的分流规则,只是在需要时把某个代理组的出口切换到远程服务器。
如果使用 TUN 模式,还必须避免代理自循环。远程服务器的 SSH 连接本身,以及本机回环地址,应该始终直连:
rules: |
否则可能出现如下环路:
SSH 连接远程服务器 |
我把这套流程整理成了一个小工具 proxyctl,其中 proxyctl clash 可以输出 Clash / Clash Verge 集成时需要的节点和防环规则。项目文档见:proxyctl Clash Integration。
6. 测试是否生效
测试 SOCKS 代理是否能访问目标网站:
curl --socks5-hostname 127.0.0.1:1080 https://www.google.com |
测试当前出口 IP:
curl --socks5-hostname 127.0.0.1:1080 https://ifconfig.me |
如果输出的是远程服务器的出口 IP,说明代理已经生效。
或者,你还可以用 https://whatismyipaddress.com/ 来查看在互联网眼中你的 IP。
原理
1. SSH 动态端口转发
这套方法的名字是 SSH Dynamic Port Forwarding,即 SSH 动态端口转发。核心命令是:
ssh -N -D 127.0.0.1:1080 -p 2010 [email protected] |
其中,-D 127.0.0.1:1080 会:
- 把本机回环地址
127.0.0.1的1080端口实现为一个 SOCKS 代理入口。 - 让本机的 SSH client 监听
127.0.0.1:1080。
注意,ssh -D 只是创建了一个本地 SOCKS 代理,并不会自动接管所有网络流量。接下来,还需要让应用主动使用这个 SOCKS 入口。
2. 让应用使用代理
接下来,还需要让应用主动使用这个 SOCKS 入口。ssh -D 只是创建了一个本地 SOCKS 代理,并不会自动接管所有网络流量。实际使用时,可以把应用分成两类。
第一类:读取 macOS 系统代理的应用
这类应用会读取 macOS 的系统代理设置。Chrome、Safari 等 GUI 应用通常属于这一类。
因此,只要在 macOS 系统代理里设置:
SOCKS5 127.0.0.1:1080
这些应用访问 HTTP/HTTPS 网站时,通常就会把流量交给这个 SOCKS 代理,再经由 SSH 隧道转发到远程服务器。
第二类:不读取 macOS 系统代理的命令行工具
很多命令行工具不会自动读取 macOS 系统代理。它们更常使用 shell 环境变量,例如:
ALL_PROXY
HTTP_PROXY
HTTPS_PROXY因此,对于
codex、brew、git clone、curl等终端程序,通常需要显式设置代理环境变量,例如:ALL_PROXY="socks5h://127.0.0.1:1080" \
HTTP_PROXY="socks5h://127.0.0.1:1080" \
HTTPS_PROXY="socks5h://127.0.0.1:1080" \
codex这里的
socks5h表示把域名解析也交给 SOCKS 代理处理,通常比socks5更稳。
当某个应用(经过配置)实际使用 127.0.0.1:1080 作为 SOCKS5 代理后,它访问网站时不会直接连接目标网站,而是先连接本机的 SOCKS 入口。随后,应用会通过 SOCKS5 协议告诉 SSH client 自己想连接的目标地址,例如:
chatgpt.com:443 |
SSH client 收到 SOCKS5 请求后,会在已有的 SSH 连接中打开一个 direct-tcpip channel,并把目标地址发送给远程服务器上的 sshd。随后,远程服务器上的 sshd 会从服务器侧发起到目标网站的 TCP 连接。
实际链路是:
本机应用 |
目标网站看到的连接来源是远程服务器,而不是本机。
3. 何为动态转发?
所谓“动态”,指的是 127.0.0.1:1080 这个本地入口是固定的,但每次 SOCKS5 请求中的目标地址可以不同。访问 ChatGPT 时,SSH 隧道转发到 ChatGPT;访问 YouTube 时,转发到 YouTube;访问 GitHub 时,转发到 GitHub。
这和普通 SSH 本地端口转发不同。普通 -L 转发需要提前写死目标地址,例如:
ssh -L 8080:example.com:80 user@server |
而 -D 不需要提前指定目标网站,它会根据每次 SOCKS5 请求中的目标地址动态建立远程连接。
如果目标网站使用 HTTPS,那么数据传输中会有两层加密:
本机应用 <-> 目标网站:HTTPS/TLS 加密 |
远程服务器上的 sshd 只负责建立连接和转发字节流。它能看到要连接的目标地址和端口,但不会解密 HTTPS 内容。
进阶:把服务器作为节点添加到代理软件
前面提到:
在本机运行:
ssh -N -D 127.0.0.1:1080 -p 2010 [email protected]会在本机创建一个 SOCKS5 代理入口:
127.0.0.1:1080只要这个 SSH 连接保持不断,本机就可以通过
127.0.0.1:1080把网络流量转发到远程服务器,再由服务器访问目标网站。
你可以通过系统代理或者设置环境变量的方式,来让本机把流量送入该代理入口。
但是,这样的做法虽然够用,但过于粗糙。因为无论是系统代理还是设置环境变量,都会让操作系统把对应 app 的所有流量都送入该入口走代理。举个例子,Chrome 浏览器对 www.baidu.com 和 www.chatgpt.com 的访问都会经由该入口走代理。但我们显然只需要让 www.chatgpt.com 的访问走代理,而 www.baidu.com 本来不需要代理就能连接。
甚至于,由于走代理之后网速肯定会慢(因为本来是本机 -> 目标网站 的连接方式变成了 本机 -> 服务器 -> 目标网站,中间多经过一次中转),我们在开启代理后对国内网站的访问还变慢了!
于是,我们要控制 “哪些流量走哪些代理甚至不走代理”,也就是说对代理做更精细的把控。这方面不需要我们去重复发明轮子,因为现在的代理软件全都具备了这个功能,都支持设置分流规则,来规定对 XX 网站的访问走直连(不经过代理),对 XX 网站的访问走这个代理,对 XX 网站的访问走那个代理...
因此,你其实只需要把该服务器,或者说该 SOCKS5 代理入口 127.0.0.1:1080 作为一个“代理节点”添加进代理软件。当然,你还需要设定分流规则等。
但是如果你本来就有一个订阅和分流规则,那你只需要使用原本的分流规则即可。
翻墙的人肯定都从机场购买了订阅,都有代理软件的配置文件,后者里面已经有现成的节点,节点组和施加于它们的分流规则。所以你只需要把该 SOCKS5 代理入口作为一个节点添加进现成的节点组,遵循原本该节点组(内的节点)所遵循的分流规则。
这方面的知识和操作步骤参见我的关于代理软件的文档。