浏览器能打开的网站,curl 却一直卡住;pip、npm、git 各种超时。你回头确认代理软件在跑、端口在监听、节点也是通的——一切正常。问题出在另一个地方。
现象
在终端里访问某个外网站点,一直超时;而同一时间在浏览器里打开同一个站点完全正常。检查代理客户端:进程在跑、端口在监听。但命令行就是不工作。
curl https://example.com
# 卡住,最后超时
# 而浏览器打开同一地址,秒开
不只是 curl。同一类现象会成批出现:pip install 卡在下载、npm install 停在 fetch、git clone 卡在 Receiving objects。换个命令行工具再试还是不行——这不是某一个工具的问题,是所有命令行工具共有的现象。
根因
关键在于代理客户端的工作模式有两种,理解这个区别就理解了一切。
一种是"系统代理"模式: 客户端帮你改写操作系统的代理设置,所有遵循系统代理的程序自动走代理。浏览器、大多数 GUI 程序属于这一类。
另一种是"本地监听"模式: 客户端只在本地开一个端口(比如 127.0.0.1:7890),谁想用谁自己连上去。它不会自动改变任何程序的行为。
命令行程序绝大多数属于后者——curl、wget、Python 的 requests、git 等等,它们默认都走直连,除非你显式告诉它们。于是"代理开着"和"这个程序在用代理"变成了两件事。
你看到端口在监听,但没有任何程序连它,所以还是直连;直连访问不了外网,就超时。代理软件对此毫不知情,它只是在等着有人来连。
更底层一点说,这是"能力"和"策略"的分离:代理软件提供的是"这里有一个能转发流量的端口"这个能力;而"要不要走它"是每个程序自己的策略。GUI 程序因为共享操作系统的系统代理设置,策略被统一了;命令行程序各自实现、各自读取环境变量,策略是分散的。
排查
判断"到底走没走代理",最快的方法是看连接的目标地址:
curl -v https://example.com 2>&1 | head -5
如果输出里显示 Trying <真实IP>:443 ... 然后卡住,说明它在直连目标站点,压根没碰代理端口。真正的代理连接会显示 Connected to 127.0.0.1 (127.0.0.1) port 7890。
再确认一下环境变量到底有没有:
env | grep -i proxy
多数情况下这里是空的——这也就解释了为什么浏览器正常、命令行不行。
解决
方式一:临时指定。 在命令里加参数(以 curl 为例):
curl -x http://127.0.0.1:7890 https://example.com
方式二:设环境变量(通用)。
export https_proxy=http://127.0.0.1:7890
export http_proxy=http://127.0.0.1:7890
export all_proxy=socks5://127.0.0.1:7890
设好之后,绝大多数命令行程序都会自动读取。可以加到 shell 的启动文件(.bashrc / .zshrc)里让它长期生效,也可以在某个脚本里局部设置。注意环境变量是进程启动时读取的——已经开着的终端不会自动感知新设置,要么 source 一下启动文件,要么重开一个终端。
不同程序的参数名不一致,用之前查一下:
wget -e use_proxy=yes -e https_proxy=http://127.0.0.1:7890 https://example.com
git config --global http.proxy http://127.0.0.1:7890
pip install --proxy http://127.0.0.1:7890 包名
另外要注意 no_proxy 环境变量——如果它包含了你本想走代理的域名,也会被强制直连。排查"设了代理还是不行"时,别忘了看一眼这一项。
延伸与预防
遇到"命令行超时但浏览器正常",第一反应就应该是检查代理是否被显式指定,而不是去怀疑网络。
可以在 shell 里加一个自检函数,切换网络环境时看一眼:
proxycheck() {
echo "http_proxy=$http_proxy"
echo "https_proxy=$https_proxy"
echo "no_proxy=$no_proxy"
}
更本质的认知是:代理是"能力",不是"策略"。 开了代理只是提供了这个能力,每个程序是否使用它,取决于程序自己的配置。GUI 程序常被系统统一设置覆盖,命令行程序绝大多数是"各自为政"。这条规律在容器里更明显——容器内既不继承宿主机的代理环境变量,也连不到宿主机的回环地址,需要单独配置。