代理开着,终端却还是超时:git、docker、npm、pip、apt 各自读哪一份配置
浏览器能开,终端不能——这不是玄学
这篇只解决一种故障:代理明明在跑,浏览器打开 GitHub 毫无问题,但 git clone 卡住、docker pull 超时、npm install 停在那里不动。
原因说穿了很简单:浏览器和命令行工具读的不是同一套设置。 你在系统里打开的那个「代理」开关,命令行工具大多数根本不看。
这一页不讲怎么加速 GitHub、怎么换镜像源——那几件事已经有专门的页面:GitHub 访问慢怎么办、Docker 拉取加速、npm / pip 镜像源配置、开发者网络配置总览。
这一页只回答一个问题:代理开着,为什么这条命令没走它,以及怎么证明。
先说结论
- 有三层代理,互不继承:操作系统的代理设置、shell 的环境变量、每个工具自己的配置文件。命令行工具只读后两层,不读第一层。
- 先证明,再动手。 一条
curl -v就能看出这条请求走没走代理——输出里有没有Uses proxy env variable那一行。 http_proxy只认小写,这是 curl 官方手册白纸黑字写的例外;其他几个变量大小写都行,小写优先。- Docker 最容易搞反:
~/.docker/config.json里的代理只影响容器,不影响docker pull。决定docker pull的是守护进程的daemon.json。 - 已经在跑的守护进程读不到你刚 export 的变量,必须重启它。
- TUN 模式绕开这一整套问题,但会和你以前留下的旧代理配置打架——排查前先把旧配置清掉。
- SSH 走 443 是 GitHub 官方给的办法,主机名是
ssh.github.com而不是github.com。
一句话原则:「我开了代理」是一句关于你的陈述,不是关于那条命令的陈述。在证明之前,别信它。
一、三层代理,谁读哪一层
| 层 | 在哪里设 | 谁会读它 | 谁不读 |
|---|---|---|---|
| ① 系统代理 | Windows 的「Internet 选项」/macOS 的网络设置,或代理客户端替你自动写入 | 浏览器、多数图形界面软件 | 几乎所有命令行工具 |
| ② 环境变量 | http_proxy / HTTPS_PROXY / ALL_PROXY / NO_PROXY | curl、pip、apt、npm(间接)、部分语言运行时 | git 的 HTTP 传输之外的部分、Docker 守护进程(需单独设) |
| ③ 工具自己的配置 | git config、daemon.json、.npmrc、pip.conf… | 只有那一个工具 | 其他所有工具 |
| ④ TUN 虚拟网卡 | 代理客户端里开启 | 全部,因为它在网络层接管 | —— |
第一行就是九成问题的答案。 代理客户端替你打开的「系统代理」,本质上是往操作系统的代理设置里写了一个地址。浏览器会去读它;git、docker、npm 不会。
第四行是另一条路:TUN 模式不依赖任何程序的自觉,它在网络层就把流量接走了。它的代价和限制见《Kill Switch 实操》第四节。
二、先证明:这条命令到底走了哪条路
在改任何配置之前,先跑这一条。它不改变任何东西,只是告诉你真相:
curl -v -o /dev/null --max-time 10 https://github.com/
看输出开头那几行带 * 的。curl 手册对 -v 的说明写明了这些前缀各代表什么:
Verbose output lines are prefixed with letters:
>header sent by curl,<header received by curl,}data sent by curl,{data received by curl,*additional info provided by curl.(带
*的行是 curl 自己给出的补充说明——它在解释自己正在做什么、做了哪些选择。)
所以你要找的就是这几行 *,它是 curl 在告诉你它挑了哪条路。两种典型的输出格式:
走了代理,格式是这样:
* Uses proxy env variable https_proxy == 'http://127.0.0.1:7890'
* Trying 127.0.0.1:7890...
没走代理,格式是这样:
* Trying 140.82.xxx.xxx:443...
* Connected to github.com (140.82.xxx.xxx) port 443
区别只有一处:Trying 后面跟的是你本机的代理端口,还是目标网站的公网地址。 上面两段是输出格式的示例,具体的 IP、端口、行数会因 curl 版本和你的环境而不同——要以你自己机器上的那一次输出为准。
先给自己立一个基准,这比照着别人的输出猜要可靠得多:找一个你确定必须走代理才能打开的地址跑一次,再找一个国内直连就通的地址(例如任意一个国内站点)跑一次,把两次的 * 行摆在一起对比。你会立刻看出哪一行是「走了代理」的标志。有了这个基准,之后任何一个工具你都可以用同一招问它:你到底走了哪条路。
配套的第二条命令,看清当前环境里到底有哪些代理变量:
env | grep -i proxy
很多「配了没用」的情况,在这一步就能看出来——变量名拼错、大小写不对、或者根本没生效。
关于大小写,curl 手册里的原话
The environment variables can be specified in lower case or upper case. The lower case version has precedence. “http_proxy” is an exception as it is only available in lower case.
(环境变量大小写均可,小写优先。
http_proxy是个例外,它只有小写形式可用。)
手册还补了一句:某些系统(例如 Windows)不区分环境变量的大小写。
NO_PROXY 会反过来咬你
同一份手册对 NO_PROXY 的说明:
This environment variable disables use of the proxy even when specified with the –proxy option.
(命中这个变量的目标会禁用代理,即使你用
--proxy显式指定了也一样。)
所以如果某个域名怎么都不走代理,先看看它是不是落在了 NO_PROXY 里。
环境变量的作用域
export 出来的变量只对当前 shell 会话以及从它启动的子进程有效。这带来两个具体后果:
- 新开一个终端窗口,变量没了;要长期生效得写进 shell 的启动文件。
- 已经在后台跑着的守护进程完全读不到——它启动的时候你还没 export 呢。Docker 的
dockerd就是典型,处理方式见第四节。
三、Git
HTTP(S) 方式
Git 的 http.proxy 配置项,官方文档原文:
Override the HTTP proxy, normally configured using the
http_proxy,https_proxy, andall_proxyenvironment variables (see curl(1)).
也就是说:Git 的 HTTP 传输底层走的是 curl 那一套,所以上面关于环境变量的规则在这里全部适用;http.proxy 的作用是在此之上做覆盖。
文档给出的取值语法是:
[protocol://][user[:password]@]proxyhost[:port][/path]
git config --global http.proxy http://127.0.0.1:7890
git config --global https.proxy http://127.0.0.1:7890
# 只给某一个站点设(推荐,影响面最小)
git config --global http.https://github.com.proxy http://127.0.0.1:7890
# 查当前生效的值
git config --get-regexp '^https?\.'
# 取消
git config --global --unset http.proxy
git config --global --unset https.proxy
「查当前生效的值」这一条不要跳过。 排查时最常见的一种情况是:你以前配过一个代理地址,后来换了端口,旧配置还在,于是 Git 一直往一个没人监听的端口发请求——表现就是超时,看起来和被墙一模一样。
SSH 方式:走 443
git config http.proxy 只管 HTTP(S)。SSH 协议完全不读它——git@github.com:xxx/yyy.git 这种地址走的是 SSH,和上面所有设置都无关。
GitHub 官方文档给了直接的办法。先测:
ssh -T -p 443 git@ssh.github.com
文档说,成功时你会看到 You've successfully authenticated, but GitHub does not provide shell access.,并特别提醒:「443 端口对应的主机名是 ssh.github.com,不是 github.com」。
通了之后,把它写进 ~/.ssh/config(以下是文档给出的原样配置段):
Host github.com
Hostname ssh.github.com
Port 443
User git
之后 ssh -T git@github.com 应该同样成功。文档还提示:切换到 443 之后第一次连接,可能会收到主机不在 known_hosts 里、或以另一个名字出现过的警告——这是预期行为,不是出了问题。
四、Docker:守护进程和客户端是两回事
这是本文最值得单独记住的一节,因为它的反直觉程度最高。
官方文档说了什么
关于 ~/.docker/config.json(也就是"客户端"那一份):
These settings are used to configure proxy environment variables for containers only, and not used as proxy settings for the Docker CLI or the Docker Engine itself.
(这些设置只用于为容器配置代理环境变量,不会用作 Docker CLI 或 Docker Engine 本身的代理设置。)
所以:在 ~/.docker/config.json 里配代理,docker pull 依然不走代理。 它影响的是你构建出来的容器里那几个环境变量。
docker pull 是守护进程去拉的,所以配置要写在守护进程那边:
{
"proxies": {
"http-proxy": "http://proxy.example.com:3128",
"https-proxy": "http://proxy.example.com:3128",
"no-proxy": "*.test.example.com,.example.org,127.0.0.0/8"
}
}
这段是 Docker 官方文档里 daemon.json 的示例结构(三个键名分别是 http-proxy、https-proxy、no-proxy,注意是连字符写法,和客户端那份的 httpProxy 驼峰写法不一样)。改完之后文档给出的生效方式是重启守护进程:
sudo systemctl restart docker
三条必须知道的附加规则
文档里三句话,每一句都对应一类踩坑:
- 「Proxy configurations specified in the
daemon.jsonare ignored by Docker Desktop.」 —— 用 Docker Desktop 的话,改daemon.json没用,要去 Docker Desktop 自己的设置里配。 - 「Configuring the daemon directly takes precedence over environment variables.」 —— 直接配置守护进程的优先级高于环境变量。两处都配了而行为不符合预期时,先看
daemon.json。 - 守护进程在启动环境里读的变量,文档列明是这六个:
HTTP_PROXY、http_proxy、HTTPS_PROXY、https_proxy、NO_PROXY、no_proxy。
systemd 方式
如果 Docker 以 systemd 服务运行,文档给的做法是建一个 drop-in 文件:
sudo mkdir -p /etc/systemd/system/docker.service.d
然后新建 /etc/systemd/system/docker.service.d/http-proxy.conf:
[Service]
Environment="HTTP_PROXY=http://proxy.example.com:3128"
Environment="HTTPS_PROXY=http://proxy.example.com:3128"
文档补充了两点:rootless 模式下路径不同,配置在各用户家目录的 ~/.config/systemd/<user>/docker.service.d/,且 systemctl 要不带 sudo、带 --user 执行;以及代理值里的特殊字符(#?!()[]{} 这类)需要做双重转义。
什么时候不该用代理
很多情况下,配镜像源比配代理更合适——镜像是把仓库搬到近处,代理是把你搬到远处。两者的取舍见《Docker 在中国》。
五、npm、pip、apt:各读各的
npm
npm 官方配置文档里两个相关的键:
proxy(默认null,类型null或 URL)https-proxy(默认null,类型null或 URL)
文档对它们的说明是:「A proxy to use for outgoing https requests. If the HTTPS_PROXY or https_proxy or HTTP_PROXY or http_proxy environment variables are set, proxy settings will be honored by the underlying make-fetch-happen library.」——也就是说 npm 会通过底层库读这四个环境变量,不一定非要写配置。
配置文件位置,文档写明:用户级配置默认在 ~/.npmrc(userconfig 项,可被 npm_config_userconfig 环境变量或 --userconfig 命令行选项覆盖);全局配置默认在 --prefix 加 etc/npmrc,例如 /usr/local/etc/npmrc。
npm config set proxy http://127.0.0.1:7890
npm config set https-proxy http://127.0.0.1:7890
npm config get proxy # 查
npm config delete proxy # 删
npm config list # 看全部,包括它从哪个文件读的
npm config list 会打印它实际读取的配置文件路径,排查时比猜有用得多。
pip
pip 的代理是一个命令行选项,官方文档原文:
--proxy <proxy>Specify a proxy in the formscheme://[user:passwd@]proxy.server:port. (environment variable:PIP_PROXY)
还有一个相关选项:--no-proxy-env,「Do not read proxy configuration from environment variables.」(环境变量 PIP_NO_PROXY_ENV)——明确不读环境变量,排查时用来做隔离测试很方便。
配置文件位置(官方文档列出的,按系统):
| 系统 | 用户级 | 全局 |
|---|---|---|
| Unix | $HOME/.config/pip/pip.conf(遵循 XDG_CONFIG_HOME),旧位置 $HOME/.pip/pip.conf 若存在也会加载 | /etc/pip.conf,以及 XDG_CONFIG_DIRS 各路径下的 pip 子目录 |
| Windows | %APPDATA%\pip\pip.ini,旧位置 %HOME%\pip\pip.ini | C:\ProgramData\pip\pip.ini(Windows 7 及以后) |
文档还说明了加载顺序是 Global → User → Site → PIP_CONFIG_FILE,后读的覆盖先读的;并给了一条自查命令:pip config debug 可以打印出确切路径。有疑问时跑这一条,不要猜。
apt
apt 的代理配置,apt-transport-http 手册原文:
The environment variable
http_proxyis supported for system wide configuration. Proxies specific to APT can be configured via the optionAcquire::http::Proxy. Proxies which should be used only for certain hosts can be specified viaAcquire::http::Proxy::host.
以及取值规则:
- URI 格式:
scheme://[[user][:pass]@]host[:port]/ - 支持的 scheme:
socks5h(SOCKS5,且由代理端做 DNS 解析)、http、https——socks5h这一项对我们的场景特别有用,因为它把域名解析也交给了代理端。 - 特殊值
DIRECT表示不使用代理;no_proxy环境变量同样被支持。
配置文件路径来自 apt.conf(5):主配置 /etc/apt/apt.conf,配置片段目录 /etc/apt/apt.conf.d/。惯例是在后者里新建一个文件,例如 /etc/apt/apt.conf.d/95proxy:
Acquire::http::Proxy "socks5h://127.0.0.1:7891";
Acquire::https::Proxy "socks5h://127.0.0.1:7891";
Acquire::http::Proxy::mirrors.example.cn "DIRECT";
最后一行演示的就是按主机例外:国内镜像源直连,其余走代理。
curl 自己
curl 是上面几个工具的底层,也是最好的测试探针:
curl -x http://127.0.0.1:7890 -v -o /dev/null https://github.com/ # 显式指定
curl --noproxy '*' -v -o /dev/null https://github.com/ # 强制不走代理
两条对比着跑,就能干净地分辨出「是代理不通」还是「直连也不通」。
六、一张表:症状 → 该改哪里
| 症状 | 大概率原因 | 改哪里 |
|---|---|---|
| 浏览器行,所有命令行都不行 | 只开了系统代理 | 设环境变量,或改用 TUN 模式 |
curl 行,git clone 不行(HTTPS 地址) | Git 里有过期的 http.proxy | git config --get-regexp '^https?\.' 查出来再改 |
git 用 HTTPS 行、SSH 地址不行 | SSH 不读 HTTP 代理配置 | ~/.ssh/config 走 443 |
docker pull 不行,其他都行 | 配在了客户端,守护进程没配 | daemon.json 的 proxies,改完重启守护进程 |
Docker Desktop 上改了 daemon.json 没反应 | 文档明写它会被忽略 | 去 Docker Desktop 自己的设置里改 |
npm install 卡住 | 变量没被读到,或 .npmrc 里有旧值 | npm config list 看它读的是哪个文件 |
pip install 卡住 | 配置文件层级弄错 | pip config debug 打印确切路径 |
apt update 卡住 | apt 不读系统代理 | /etc/apt/apt.conf.d/ 下加一个片段 |
| 改完变量守护进程仍不生效 | 它启动时读不到 | 重启该服务 |
| 某个域名怎么都不走代理 | 命中了 NO_PROXY | 检查 NO_PROXY 的内容 |
全部都不行,连 curl 直连也超时 | 不是代理配置问题 | 见《Clash 报错对照表》、《DNS 污染自测》 |
七、TUN 模式:绕开这一整套,但有前提
TUN 通过虚拟网卡在网络层接管流量,因此不依赖任何程序自觉去读代理设置——git、docker、apt 全都自动生效。对于「工具太多、配不过来」的情况,这是最省事的路。
但要注意两件事:
- 旧配置会打架。 你以前写过的
http.proxy、.npmrc里的proxy、daemon.json里的proxies都还在。TUN 开着的时候,这些配置会把请求先送到那个地址——如果那个端口现在没人监听,结果就是失败,而你会误以为是 TUN 没生效。排查 TUN 时,先把这些旧配置清掉或临时注释掉。 - TUN 需要权限,开不起来时客户端会明确提示。具体报错和处理见《Clash 报错对照表》的 TUN 一节。
验证方法还是第二节那条 curl -v:TUN 正常工作时输出里不会出现 Uses proxy env variable,但 Trying 到的地址和路径与不开时不同。想看出口本身有没有变,用 IP 地址查询;想确认 DNS 没有绕过去,用 DNS 泄露检测。
八、什么时候代理根本不是答案
最后说一句反方向的话:这一类问题里,有相当一部分的最优解不是配代理,而是换源。
- 拉取公共镜像和依赖包:国内镜像源几乎总是更快,而且不占你的代理流量。见《npm / pip 国内镜像源配置》、《Docker 在中国》。
- 只是
git clone慢,不是完全不通:先试 SSH 走 443,再考虑代理。见《GitHub 访问慢怎么办》。 - 整机的开发网络怎么搭:《开发者网络配置总览》 是那一篇。
代理适合的是「非它不可」的流量(拉取被阻断的资源、访问只有境外才有的服务),不适合当成所有网络问题的万能答案。分清楚这一点,你要配的东西会少很多。
参考资料
以下文档均于 2026 年 9 月 20—21 日核对。文中所有命令输出都是格式示例,用来告诉你该看哪一行;请以你自己机器上跑出来的那一次为准。
Git / GitHub
git config官方文档 —http.proxy— 「Override the HTTP proxy, normally configured using thehttp_proxy,https_proxy, andall_proxyenvironment variables」及取值语法- GitHub Docs — Using SSH over the HTTPS port —
ssh -T -p 443 git@ssh.github.com测试命令、~/.ssh/config配置段、「443 端口的主机名是ssh.github.com而非github.com」的提醒、known_hosts警告说明
Docker
- Docker Docs — Configure the daemon to use a proxy —
daemon.json的proxies结构(http-proxy/https-proxy/no-proxy)、「daemon.json 中的代理配置会被 Docker Desktop 忽略」、「直接配置守护进程优先于环境变量」、守护进程读取的六个环境变量、systemd drop-in 路径与 rootless 模式差异 - Docker Docs — Configure the Docker CLI to use a proxy —
~/.docker/config.json的proxies.default结构,以及「这些设置只用于为容器配置代理环境变量,不用作 Docker CLI 或 Docker Engine 本身的代理设置」
包管理器
- npm Docs — config —
proxy/https-proxy的定义与默认值、底层make-fetch-happen读取的四个环境变量、userconfig(~/.npmrc)与globalconfig的默认路径 - pip 文档 — Configuration — 各系统下
pip.conf/pip.ini的全局、用户、site 三级路径,加载顺序,以及pip config debug自查方法 - pip 文档 — General Options —
--proxy(PIP_PROXY)与--no-proxy-env(PIP_NO_PROXY_ENV) - Debian manpages —
apt-transport-http(1)—Acquire::http::Proxy、按主机的Acquire::http::Proxy::host、URI 格式、支持的socks5h/http/httpsscheme、特殊值DIRECT - Debian manpages —
apt.conf(5)—/etc/apt/apt.conf与/etc/apt/apt.conf.d/的定义
curl
- curl 官方手册 —
-v/--verbose各前缀的含义(*= additional info provided by curl)、环境变量大小写规则(小写优先,http_proxy仅有小写形式)、ALL_PROXY的回退含义、NO_PROXY会覆盖--proxy的说明
将本指南加入收藏夹
跨境网络环境瞬息万变。建议按下 Ctrl+D (Windows) 或 Cmd+D (Mac) 收藏本页,以便在连接波动时快速查阅解决方案。
加入 5,000+ 跨境从业者,第一时间获取最新的 GFW 封锁动态与协议升级提醒。
* 我们绝不发送垃圾邮件,您可以随时取消订阅。
更多关于 Howto 的解析
Clash 报错对照表:订阅导入失败、连不上节点、DNS 出错的日志怎么读
屏幕上那行红字到底什么意思?按错误原文查,每条都标了它出自哪个客户端的哪个文件,以及在本机能做的检查。所有修法都是客户端 …
DNS 污染自测:三条命令,分清污染、劫持、节点故障和网站自己挂了
一个网站打不开、别的都正常时,怎么用三条命令定位原因。每条命令的参数出自工具自己的手册,每种输出对应哪一种病,文末有一张 …
Kill Switch 实操:在 Windows 和 macOS 上把「隧道一断就断网」做出来,并验证它真的生效
不是讲概念,是把它配出来:Windows 用防火墙默认出站策略,macOS 用系统自带的 pf。每一步都标了微软或苹果官 …
KUAJIE VPN