代理开着,终端却还是超时:git、docker、npm、pip、apt 各自读哪一份配置

浏览器能开,终端不能——这不是玄学

这篇只解决一种故障:代理明明在跑,浏览器打开 GitHub 毫无问题,但 git clone 卡住、docker pull 超时、npm install 停在那里不动。

原因说穿了很简单:浏览器和命令行工具读的不是同一套设置。 你在系统里打开的那个「代理」开关,命令行工具大多数根本不看。

这一页不讲怎么加速 GitHub、怎么换镜像源——那几件事已经有专门的页面:GitHub 访问慢怎么办Docker 拉取加速npm / pip 镜像源配置开发者网络配置总览

这一页只回答一个问题:代理开着,为什么这条命令没走它,以及怎么证明。

先说结论

  1. 有三层代理,互不继承:操作系统的代理设置、shell 的环境变量、每个工具自己的配置文件。命令行工具只读后两层,不读第一层
  2. 先证明,再动手。 一条 curl -v 就能看出这条请求走没走代理——输出里有没有 Uses proxy env variable 那一行。
  3. http_proxy 只认小写,这是 curl 官方手册白纸黑字写的例外;其他几个变量大小写都行,小写优先。
  4. Docker 最容易搞反~/.docker/config.json 里的代理只影响容器,不影响 docker pull。决定 docker pull 的是守护进程的 daemon.json
  5. 已经在跑的守护进程读不到你刚 export 的变量,必须重启它。
  6. TUN 模式绕开这一整套问题,但会和你以前留下的旧代理配置打架——排查前先把旧配置清掉。
  7. SSH 走 443 是 GitHub 官方给的办法,主机名是 ssh.github.com 而不是 github.com

一句话原则:「我开了代理」是一句关于的陈述,不是关于那条命令的陈述。在证明之前,别信它。

一、三层代理,谁读哪一层

在哪里设谁会读它谁不读
① 系统代理Windows 的「Internet 选项」/macOS 的网络设置,或代理客户端替你自动写入浏览器、多数图形界面软件几乎所有命令行工具
② 环境变量http_proxy / HTTPS_PROXY / ALL_PROXY / NO_PROXYcurl、pip、apt、npm(间接)、部分语言运行时git 的 HTTP 传输之外的部分、Docker 守护进程(需单独设)
③ 工具自己的配置git configdaemon.json.npmrcpip.conf只有那一个工具其他所有工具
④ TUN 虚拟网卡代理客户端里开启全部,因为它在网络层接管——

第一行就是九成问题的答案。 代理客户端替你打开的「系统代理」,本质上是往操作系统的代理设置里写了一个地址。浏览器会去读它;gitdockernpm 不会。

第四行是另一条路: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, and all_proxy environment 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-proxyhttps-proxyno-proxy注意是连字符写法,和客户端那份的 httpProxy 驼峰写法不一样)。改完之后文档给出的生效方式是重启守护进程:

sudo systemctl restart docker

三条必须知道的附加规则

文档里三句话,每一句都对应一类踩坑:

  1. 「Proxy configurations specified in the daemon.json are ignored by Docker Desktop.」 —— 用 Docker Desktop 的话,改 daemon.json 没用,要去 Docker Desktop 自己的设置里配。
  2. 「Configuring the daemon directly takes precedence over environment variables.」 —— 直接配置守护进程的优先级高于环境变量。两处都配了而行为不符合预期时,先看 daemon.json
  3. 守护进程在启动环境里读的变量,文档列明是这六个:HTTP_PROXYhttp_proxyHTTPS_PROXYhttps_proxyNO_PROXYno_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 会通过底层库读这四个环境变量,不一定非要写配置。

配置文件位置,文档写明:用户级配置默认在 ~/.npmrcuserconfig 项,可被 npm_config_userconfig 环境变量或 --userconfig 命令行选项覆盖);全局配置默认在 --prefixetc/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 form scheme://[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.iniC:\ProgramData\pip\pip.ini(Windows 7 及以后)

文档还说明了加载顺序是 Global → User → Site → PIP_CONFIG_FILE,后读的覆盖先读的;并给了一条自查命令:pip config debug 可以打印出确切路径。有疑问时跑这一条,不要猜。

apt

apt 的代理配置,apt-transport-http 手册原文:

The environment variable http_proxy is supported for system wide configuration. Proxies specific to APT can be configured via the option Acquire::http::Proxy. Proxies which should be used only for certain hosts can be specified via Acquire::http::Proxy::host.

以及取值规则:

  • URI 格式:scheme://[[user][:pass]@]host[:port]/
  • 支持的 scheme:socks5h(SOCKS5,且由代理端做 DNS 解析)、httphttps —— 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.proxygit config --get-regexp '^https?\.' 查出来再改
git 用 HTTPS 行、SSH 地址不行SSH 不读 HTTP 代理配置~/.ssh/config 走 443
docker pull 不行,其他都行配在了客户端,守护进程没配daemon.jsonproxies,改完重启守护进程
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 通过虚拟网卡在网络层接管流量,因此不依赖任何程序自觉去读代理设置——gitdockerapt 全都自动生效。对于「工具太多、配不过来」的情况,这是最省事的路。

但要注意两件事:

  1. 旧配置会打架。 你以前写过的 http.proxy.npmrc 里的 proxydaemon.json 里的 proxies 都还在。TUN 开着的时候,这些配置会把请求先送到那个地址——如果那个端口现在没人监听,结果就是失败,而你会误以为是 TUN 没生效。排查 TUN 时,先把这些旧配置清掉或临时注释掉。
  2. TUN 需要权限,开不起来时客户端会明确提示。具体报错和处理见《Clash 报错对照表》的 TUN 一节。

验证方法还是第二节那条 curl -v:TUN 正常工作时输出里不会出现 Uses proxy env variable,但 Trying 到的地址和路径与不开时不同。想看出口本身有没有变,用 IP 地址查询;想确认 DNS 没有绕过去,用 DNS 泄露检测

八、什么时候代理根本不是答案

最后说一句反方向的话:这一类问题里,有相当一部分的最优解不是配代理,而是换源。

代理适合的是「非它不可」的流量(拉取被阻断的资源、访问只有境外才有的服务),不适合当成所有网络问题的万能答案。分清楚这一点,你要配的东西会少很多。

参考资料

以下文档均于 2026 年 9 月 20—21 日核对。文中所有命令输出都是格式示例,用来告诉你该看哪一行;请以你自己机器上跑出来的那一次为准。

Git / GitHub

Docker

  • Docker Docs — Configure the daemon to use a proxydaemon.jsonproxies 结构(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.jsonproxies.default 结构,以及「这些设置只用于为容器配置代理环境变量,不用作 Docker CLI 或 Docker Engine 本身的代理设置」

包管理器

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 封锁动态与协议升级提醒。

* 我们绝不发送垃圾邮件,您可以随时取消订阅。

← 家里的路由器要不要做透明代理?三种方案的真实代价