如何使用远程服务器作为本机的代理

这两年代理软件管得比较严,但 ChatGPT 对科研和学习又不可或缺。通常的解决办法是找更稳定的代理节点,也就是俗称的“机场”。不过,如果你手上有一台本身已经能访问外网的远程服务器(别管是怎么来的),也可以换一种思路:让本机通过 SSH 连接这台服务器,再把它作为本机的网络出口。这样一来,本机访问网站时,流量会先走到服务器,再由服务器访问目标网站。

如何使用远程服务器作为本机的代理

本文介绍的方法依赖一个前提:远程服务器本身已经可以访问目标网站,而本机可以通过 SSH 登录这台服务器。

这种服务器在企业、学校或实验室环境中并不少见,我就不详细讲了。

如果服务器已经配置好了外网访问能力,那么我们就可以利用 SSH 动态端口转发,在本机创建一个 SOCKS 代理入口,让本机流量经由这台服务器发出。

直观地说:

1
2
3
4
本机
-> SSH 隧道
-> 远程服务器
-> 目标网站

因此,只要服务器能访问某个网站,本机也可以通过这个隧道,借由服务器访问它。

使用方法

假设远程服务器信息如下:

1
2
3
服务器 IP: 1.2.3.4
SSH 端口: 2010
用户名: luyukuan

1. 在本机创建 SOCKS5 代理入口

在本机运行:

1
ssh -N -D 127.0.0.1:1080 -p 2010 [email protected]

这条命令会在本机创建一个 SOCKS5 代理入口:

1
127.0.0.1:1080

只要这个 SSH 连接保持不断,本机就可以通过 127.0.0.1:1080 把网络流量转发到远程服务器,再由服务器访问目标网站。

如果服务器已经写入 ~/.ssh/config

1
2
3
4
Host Tsinghua
HostName 1.2.3.4
User luyukuan
Port 2010

那么命令可以简化为:

1
ssh -N -D 127.0.0.1:1080 Tsinghua

2. 让 GUI 应用使用系统代理

对于 Chrome、Safari 等 GUI 应用,通常可以直接使用 macOS 系统代理。

打开 macOS 设置:

1
2
3
4
5
System Settings
-> Network
-> Wi-Fi
-> Details
-> Proxies

开启:

1
SOCKS Proxy

填写:

1
2
Server: 127.0.0.1
Port: 1080

保存后,Chrome、Safari 等支持系统代理的应用通常就会使用这个代理。此时访问 ChatGPT、YouTube 等网站时,流量路径大致是:

1
2
3
4
5
Chrome
-> macOS SOCKS 代理 127.0.0.1:1080
-> SSH 隧道
-> 远程服务器
-> 目标网站

目标网站看到的出口 IP 是远程服务器的 IP,而不是本机的 IP。

3. 让终端程序使用代理

需要注意的是,macOS 系统代理通常不会自动作用于终端程序。例如:

  • codex
  • brew
  • git clone
  • curl
  • pip
  • npm

这些程序更常见的代理配置方式是环境变量。

如果要让 Codex CLI 使用该 SOCKS 代理,可以这样启动:

1
2
3
4
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

例如,如果要启动 Codex App 并让它使用代理:

1
2
3
4
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 app

这里推荐使用 socks5h://,而不是 socks5://。区别是:socks5h 会把域名解析也交给代理侧处理,避免本机 DNS 解析失败或泄漏。

对于其他终端程序,也可以用同样的方式。例如:

1
ALL_PROXY="socks5h://127.0.0.1:1080" git clone <repo-url>

或者:

1
ALL_PROXY="socks5h://127.0.0.1:1080" brew update

4. 写成脚本

如果经常使用,可以把启动命令和环境变量写成脚本。例如可以写一个 codex-proxy,每次自动带代理启动 Codex。

这类脚本很简单,不必手写很久。把当前代理端口和目标程序告诉 AI,就可以直接生成。

不建议把代理环境变量无脑写进全局 ~/.zshrc,因为这样会影响所有终端程序,包括 sshgitcondapip 等。更推荐为特定程序写单独的 wrapper。

5. 测试是否生效

测试 SOCKS 代理是否能访问目标网站:

1
curl --socks5-hostname 127.0.0.1:1080 https://www.google.com

测试当前出口 IP:

1
curl --socks5-hostname 127.0.0.1:1080 https://ifconfig.me

如果输出的是远程服务器的出口 IP,说明代理已经生效。

或者,你还可以用 https://whatismyipaddress.com/ 来查看在互联网眼中你的 IP。

原理

1. SSH 动态端口转发

这套方法的名字是 SSH Dynamic Port Forwarding,即 SSH 动态端口转发。核心命令是:

1
ssh -N -D 127.0.0.1:1080 -p 2010 [email protected]

其中,-D 127.0.0.1:1080 会:

  1. 把本机回环地址 127.0.0.11080 端口实现为一个 SOCKS 代理入口。
  2. 让本机的 SSH client 监听 127.0.0.1:1080

注意,ssh -D 只是创建了一个本地 SOCKS 代理,并不会自动接管所有网络流量。接下来,还需要让应用主动使用这个 SOCKS 入口。

2. 让应用使用代理

接下来,还需要让应用主动使用这个 SOCKS 入口。ssh -D 只是创建了一个本地 SOCKS 代理,并不会自动接管所有网络流量。实际使用时,可以把应用分成两类。

  1. 第一类:读取 macOS 系统代理的应用

    这类应用会读取 macOS 的系统代理设置。Chrome、Safari 等 GUI 应用通常属于这一类。

    因此,只要在 macOS 系统代理里设置:

    1
    SOCKS5 127.0.0.1:1080

    这些应用访问 HTTP/HTTPS 网站时,通常就会把流量交给这个 SOCKS 代理,再经由 SSH 隧道转发到远程服务器。

  2. 第二类:不读取 macOS 系统代理的命令行工具

    很多命令行工具不会自动读取 macOS 系统代理。它们更常使用 shell 环境变量,例如:

    1
    2
    3
    ALL_PROXY
    HTTP_PROXY
    HTTPS_PROXY

    因此,对于 codexbrewgit clonecurl 等终端程序,通常需要显式设置代理环境变量,例如:

    1
    2
    3
    4
    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 自己想连接的目标地址,例如:

1
2
3
chatgpt.com:443
youtube.com:443
github.com:443

SSH client 收到 SOCKS5 请求后,会在已有的 SSH 连接中打开一个 direct-tcpip channel,并把目标地址发送给远程服务器上的 sshd。随后,远程服务器上的 sshd 会从服务器侧发起到目标网站的 TCP 连接。

实际链路是:

1
2
3
4
5
6
本机应用
-> 本机 SOCKS5 入口 127.0.0.1:1080
-> 本机 SSH client
-> SSH 加密隧道
-> 远程服务器 sshd
-> 目标网站

目标网站看到的连接来源是远程服务器,而不是本机。

3. 何为动态转发?

所谓“动态”,指的是 127.0.0.1:1080 这个本地入口是固定的,但每次 SOCKS5 请求中的目标地址可以不同。访问 ChatGPT 时,SSH 隧道转发到 ChatGPT;访问 YouTube 时,转发到 YouTube;访问 GitHub 时,转发到 GitHub。

这和普通 SSH 本地端口转发不同。普通 -L 转发需要提前写死目标地址,例如:

1
ssh -L 8080:example.com:80 user@server

-D 不需要提前指定目标网站,它会根据每次 SOCKS5 请求中的目标地址动态建立远程连接。

如果目标网站使用 HTTPS,那么数据传输中会有两层加密:

1
2
本机应用 <-> 目标网站:HTTPS/TLS 加密
本机 SSH client <-> 远程服务器 sshd:SSH 加密

远程服务器上的 sshd 只负责建立连接和转发字节流。它能看到要连接的目标地址和端口,但不会解密 HTTPS 内容。

1