你在终端敲下 git push,等推送完成。几秒后报错了,说连不上某个本地端口的代理。你有点纳闷——上次明明能推。
现象
推送代码时报错:
fatal: unable to access '...': Failed to connect to 127.0.0.1 port 7890:
Connection refused
看起来像网络问题,但目标站点其实是国内的,直连完全可行。而且同一台机器上浏览器打开该站点毫无问题。错误信息里的 Connection refused 是关键——不是超时,是连接被拒绝,说明那个端口后面没有任何东西在监听。
这里有个容易混淆的点:报错里出现的 127.0.0.1 很容易被当成"远程服务地址",实际上它是本机地址。也就是说,git 是在尝试连本机的一个端口,而不是在访问远端仓库——这一眼就能把方向从"网络问题"扭到"本地配置问题"。
根因
本机的 git 配置里(局部 .git/config 和全局 ~/.gitconfig 都)写了走本地代理——这是之前为了拉取国外仓库配的,一直留着。
而这次推送的目标是国内站点,直连毫无问题。问题在于代理软件当时根本没开。于是 git 忠实地按配置去连 127.0.0.1:7890,得到的自然是 Connection refused。
所以真正的原因不是"网络不通",而是"git 被配置强制走了一个不存在的代理"。浏览器的系统代理设置里可能配了自动绕过(或者代理软件没开时浏览器会自动回退直连),所以浏览器没事——但 git 只认自己的配置。
顺带解释一个常见困惑:为什么"上次明明能推"?因为上次代理软件开着,或者上次推的是需要代理的国外仓库。配置没变,变的是"运行时环境"——这正是配置残留类问题最难排查的地方:它不报"配置错",只在特定时刻才暴露。
解决
有几种思路,推荐最小侵入的那种。
不改动全局配置,只对本次命令生效:
git -c http.proxy= -c https.proxy= push
这里 -c http.proxy= 的作用是把代理显式置空——用空字符串覆盖掉配置文件里的值。全局配置纹丝不动,本次推送走直连,一步到位。它之所以有效,是因为命令行参数的优先级高于配置文件。
如果经常需要用,可以做成别名:
git config --global alias.dpush '!git -c http.proxy= -c https.proxy= push'
# 之后用
git dpush
不建议为了这一次去删全局配置——因为下次你拉国外仓库时可能还需要它。删掉之后过几天又要重新配,反而更折腾。
顺便记一下另一种情况的处理:如果代理软件开着但配置的端口不对(比如代理实际监听在 7891,配置写的是 7890),同样会 Connection refused。这时先确认端口:
ss -tln | grep -E '789[0-9]'
再按实际端口改配置,或者用上面的方式临时清空。要快速看清 git 当前到底用了什么代理配置,可以这样查:
git config --show-origin --get-regexp 'proxy'
--show-origin 会告诉你每一项来自哪个文件,避免"改了全局却没生效,其实是局部文件在覆盖"的困惑。
延伸与预防
核心习惯只有一句话:改配置用最小作用域。
全局配置影响所有仓库、所有时间;命令行参数只影响这一次。当你要临时改变某个行为时,优先用命令行覆盖而不是改文件。
另外值得记一次这个诊断信号:Connection refused 和 timeout 表示完全不同的两件事。
Connection refused:目标端口没人监听(服务没开、端口不对、防火墙 REJECT)。timeout:包发出去了但没响应(网络不通、被丢弃、防火墙 DROP)。
看到 refused 先去查"那个服务在不在",看到 timeout 才去查"路径通不通"。这个区分能在排查时省下大量时间。
最后一条预防:给"需要代理"和"不需要代理"的操作分别准备命令或别名,把代理配置集中在明确的入口(比如只在拉取国外仓库时显式指定),而不是"全局配一次、之后不管"。配置越少、越集中,这类残留问题就越不容易冒出来。