GitHub克隆/推送速度慢这个话题,几乎隔几天就会在技术群里被捞起来一次。症状非常固定:git clone跑了一半,进度条卡在Receiving objects,接着一行RPC failed; curl 18 transfer closed with outstanding read data remaining直接终结任务;或者git push推到 90%,客户端报The remote end hung up unexpectedly。很多人第一时间会想换网络、上工具,但其实不依赖任何外部网络调整,仅靠 3 行 Git 指令,就能把绝大多数“能连但慢、慢到断”的问题压下去。这篇文章不谈玄学,只讲指令原理、适用边界和一套可复制的排查思路,适合总在 Git 传输上翻车的开发者、运维和经常维护开源仓库的人。
1. 先判断:你的慢是“真慢”还是“假断”
1.1 典型症状:进度条卡死的三种现场
Git 传输慢和网络下载慢不一样,它很少真的“匀速慢”,更多是“一阵一阵卡死,最后连都连不上”。我自己见过三种高频现场:
第一种,Enumerating objects阶段卡住。仓库只有几百兆,但 Git 要先把所有对象枚举出来,这个阶段如果长时间不动,往往是服务器端在压缩包,或者本地 DNS 解析后连到的节点链路质量很差。此时git clone -v能看到 TCP 连接已建立,但数据就是不动。
第二种,Receiving objects进度条停在某个百分比,比如 37%、68%,过几十秒后直接报错。这类是最典型的“低速误杀”或者“缓冲区溢出”。不是断开,而是 Git 在等待数据传输时,发现一段时间内速度低于阈值,主动放弃了。
第三种,git push时推不上去。本地仓库挺完整,前面编译、提交都正常,但一 push,数据包发到一半,远端就挂断了。这种通常是 HTTP 通道在传输大对象时缓冲设置不够,或者网络中间设备掐断了长时间空闲的连接。
这三个场景的共同点是:网络链路并没有彻底不可用,Git 客户端却在传输层做了过多“安全动作”。知道这一点很重要,因为后面那 3 行 Git 指令,本质上就是用来关掉这些动作的。
1.2 用 git 输出和错误码锁定故障点
在动手改配置前,我建议先花两分钟复现一下问题,拿到更多信息。不要上来就复制网上一堆配置,那样出了问题都不知道是哪条引起的。
可以先加verbose参数重跑一次:
git clone -v https://github.com/user/repo.git如果想看到底层 HTTP 传输细节,还可以临时打开两个环境变量:
export GIT_TRACE=1 export GIT_CURL_VERBOSE=1 git clone https://github.com/user/repo.git你会看到大量的curl日志,包括连接了哪个 IP、TLS 握手用时多少、是哪个阶段断开的。看到这些之后,再对照下面的错误码就清楚多了:
| 报错片段 | 大概率原因 |
|---|---|
curl 18 transfer closed with outstanding read data remaining | 连接建立后数据传输中断 |
curl 56 failure when receiving data from the peer | 对端发完数据前关闭了连接 |
Unable to rewind rpc post data | HTTP POST 缓冲回卷失败,常见于大文件推送 |
The remote end hung up unexpectedly | 服务端主动断开,也可能是本地缓冲太小 |
Failed to connect to github.com port 443: Connection timed out | TCP 层面连不上,改配置无效 |
如果你发现错误集中在“连接建立后但传输中途断开”,那恭喜,这正是 3 行 Git 指令的射程范围。如果是Connection timed out,那属于 TCP 链路问题,后面第 4 章会讲对应的处理思路。
2. 3 行 Git 指令拆解:为什么这三条能续命
网上一提到 GitHub 克隆慢,就会有人贴出三条命令,但很少解释它们到底改了什么。这里先给出完整的三行:
git config --global http.postBuffer 524288000 git config --global http.lowSpeedLimit 1 git config --global http.lowSpeedTime 999999这三行的作用不是“加速”,而是不要因为传输慢而自杀。放在一个生活化的类比里:你点了一份外卖,配送员在路上堵车,你要做的不是催他每三秒报一次位置,而是把“我等不下去”的时限从 3 分钟改成 11 天。Git 默认有些策略太激进,碰到跨地域的不稳定链路时,会把“卡顿”误判为“断连”,然后主动放弃。
2.1 第一行:postBuffer 解决“写不进去”的假死
http.postBuffer改的是 HTTP 发送缓冲区的大小,默认值通常是 1MB。这个值的影响在推送大对象时尤其明显。
你可以把 Git 通过 HTTPS 推送数据,想象成用一根吸管往瓶子里注水。吸管很细没关系,但如果储水槽只有 1MB,水灌满之后,Git 就必须停下来等待服务端确认,确认完了再倒下一批。如果这个确认环节因为网络延迟变得很慢,吸管里面就容易出现“堵住”的现象,最终连接假死。把缓冲区调到 500MB 左右,相当于换了个大储水槽,同样一次推送可以少很多次停下来等待,整个流程顺滑得多。
524288000这个数字就是 500MB。如果你的机器内存比较小,比如只有 1GB,也可以折中用536870912或者降到52428800(50MB)。我用的时候发现,500MB 在绝大多数开发机上都没有压力,Git 也不是真的把 500MB 全塞在内存里,只是给了一个上限。
2.2 第二、三行:lowSpeedLimit/lowSpeedTime 关掉“误杀”
http.lowSpeedLimit和http.lowSpeedTime是一对组合参数,含义是:当传输速度低于lowSpeedLimit字节/秒,并且持续了lowSpeedTime秒,Git 就认为这条连接已经没有价值,主动中断。
默认情况下,这个阈值和持续时间都是 0,理论上不会触发。但问题在于,很多人用的系统发行版、Git GUI 客户端或者历史遗留的全局配置,会在不知情的情况下注入一组数值。比如有的环境默认把lowSpeedLimit设成了 1000,lowSpeedTime设成了 30。这在局域网没问题,但跨境访问 GitHub 时,瞬时速度低于 1KB/s 太常见了,只要持续 30 秒,Git 就会决定“你太慢了,我不等了”。
所以第二行把速度阈值设成1,第三行把持续时间设成999999,等效于:只要传输速度没有完全变成 0,并且持续 11 天以上,Git 就绝不主动断开。这会让你在慢速但不中断的网络里,慢慢磨也能把仓库拉完。
2.3 三行指令的正确落地姿势
三条命令执行完后,建议校验一下,避免你改了全局配置但被路径下的局部配置覆盖:
git config --global --list | grep http如果看到了三条配置,说明已经生效。接着可以重新跑一次刚才失败的操作。注意一点:这三行只对 HTTPS 方式的 Git 传输生效,对你用浏览器下载 zip 包、或者用 SSH 协议都没有作用。
另外,如果是公司统一维护的 Git 服务器,别在全局配置里塞这些值,直接使用:
git -c http.postBuffer=524288000 -c http.lowSpeedLimit=1 -c http.lowSpeedTime=999999 clone https://...这样可以做到只针对当前这条命令生效,不影响其他仓库。
3. 光改配置不够:配合浅克隆和 SSH,把传输量降一半
3.1 浅克隆与单分支:只下载你需要的提交
如果仓库本身很大,比如历史提交几千次、里面还躺着不少大文件,那么再怎么调缓冲也只能保证“不死”,并不能让速度变快。真正的提速逻辑是:少传数据。
Git 官方早就提供了浅克隆方案,你可以只取最新的一次提交:
git clone --depth 1 --single-branch --branch main https://github.com/user/repo.git--depth 1表示只拉取最近 1 条提交记录,--single-branch表示只拉main分支。这样克隆下来的仓库体积,往往只有完整仓库的十分之一甚至更小。原因是 Git 不再需要为历史上所有版本生成打包数据,服务端的 CPU 开销也大幅降低,通常能明显缓解Enumerating objects阶段的卡顿。
如果你已经完成了一个全量克隆,只是想裁掉历史,也可以用:
git fetch --depth 1这会让你本地变为浅仓库。但要注意,浅克隆对后续的git push有限制,如果你想给这个仓库提交代码并推送,通常需要先执行git fetch --unshallow拉全历史。所以我的建议是:以读为主就浅克隆,以写为主还是老老实实全量拉取。
3.2 SSH 认证协议:绕开 HTTP 层的额外开销
HTTPS 克隆慢,除了网络因素,还因为 HTTP 层做了很多 TLS 握手的开销,每次连接都要经历一次完整的证书验证。而 SSH 协议走的是长连接,对频繁操作更友好,也更稳定。
切换方式很简单,先生成一对密钥,如果已经有的话可以跳过:
ssh-keygen -t ed25519 -C "你的邮箱"然后把~/.ssh/id_ed25519.pub的内容复制到 GitHub 的 SSH keys 设置页。接下来在仓库目录里执行:
git remote set-url origin git@github.com:user/repo.git用ssh -T git@github.com验证一下,通了之后,后续的 clone 和 push 都会走 SSH 通道。我自己维护开源项目时,凡是频繁 push 的仓库都换成了 SSH,基本没再遇到 HTTP 通道那种莫名的中断。
3.3 大仓库推送技巧
如果你的仓库里有大量图片、压缩包,即使三行配置和 SSH 都用了,push 依然可能很吃力。这时候还可以考虑 Git LFS(Large File Storage),把图片、二进制文件托管到 LFS 存储里。
git lfs install git lfs track "*.zip" git add .gitattributes git commit -m "track zip files with LFS" git push origin mainLFS 本身也是 Git 官方扩展,好处是普通小文件照常走 Git 协议,大文件单独通过 LFS 端点传输。但要注意 GitHub 免费版有 LFS 配额限制,超大仓库还是需要规划一下。
还有一个很多人忽略的点:git push之前先跑一次git fsck检查对象完整性。本地仓库如果有一些损坏对象,推送时会导致服务端反复尝试,速度感和实际耗费时间都会被放大。
4. 从报错反推:常见 GitHub 克隆失败场景与对症下药
4.1 RPC failed; curl 18 / 56 的排查
这条报错是 GitHub 克隆慢场景里的“主角”。先说结论:出现curl 18时,按顺序做三件事,能解决八成问题。
第一步,执行那三行 Git 指令。这能排除客户端主动断连的因素。第二步,如果还是断,就换 SSH 协议,因为 SSH 不依赖 HTTP 的缓冲机制。第三步,用浅克隆测试,把问题缩小到“仓库本身数据量太大”还是“网络传输链路不稳定”。
curl 56和curl 18技术细节不同,现象却很相似,都是接收数据时中断。除了上面三步,还可以尝试强制使用 HTTP/1.1,避免 HTTP/2 多路复用在弱网环境下的连接重置:
git config --global http.version HTTP/1.1这条配置对某些网络环境效果明显。要命的是,很多人不知道它和 postBuffer 是互补关系,两者一起配上之后,原本频繁断开的克隆,很多就一次通过了。
4.2 “Unable to rewind rpc post data”与缓冲区
如果你的报错里出现Unable to rewind rpc post data,那基本就是 HTTP POST 缓冲区的锅。这个错误在推送大对象时特别常见,Git 试图重新定位 HTTP POST 的数据流位置,但因为缓冲区太小或者网络不稳定而失败。
处理方法依然是把http.postBuffer调大。如果调大后依然无效,还可以尝试降低打包时的压缩窗口,减少内存负担:
git config --global pack.window 0pack.window控制 Git 在打包时对对象做增量压缩的“参考窗口”大小。默认值越大,压缩率越高,但消耗内存和 CPU 也越多。在低速网络上,过高的压缩率意味着服务端需要更多时间准备数据包,客户端也需要更多时间解压,反而拖慢整体速度。把这个值设成 0,相当于放弃增量压缩,直接传完整对象,虽然名义上“数据量更大”,但因为省掉了前后端的压缩等待,实际体验常常更快。
4.3 推送时 “error: failed to push some refs” 的处理
这个报错很多人误以为是速度问题,其实它是“远端有更新,本地没有拉取”导致的拒绝推送。但在跨域环境下,拉取本身也会触发前面那些网络问题,所以你要做的不是硬推,而是先把同步做好。
推荐设置:
git config --global pull.rebase true然后每次推送前,先git pull --rebase把本地改动重新放到远端最新提交之后,再执行git push。这样做的好处是,如果远端有新提交,你不会因为 merge 提交而和上游历史纠缠。拉取时也可以用git fetch --depth 1再 rebase,降低历史数据消耗。
如果这还解决不了,再看看是否本地仓库有对象损坏,或者分支名和远端不一致。很多人卡在这一步,其实是把问题归错方向了。
5. 一套完整的提速方案:从 clone 到 push 的实战复盘
5.1 实践场景:克隆一个带大量图片的 Hugo 博客仓库
我自己维护过一个 Hugo 博客仓库,主题里塞了大量截图和压缩包,完整仓库差不多 2.3GB,历史提交 2000 多次。在未做任何配置之前,在国内网络环境下执行git clone https://github.com/user/blog.git,每次都会卡在Receiving objects的 70% 左右,然后报curl 18。
当时的电脑是 Windows 10,Git 版本 2.39,属于最常见的环境。我记录了一下修复全过程,你可以完全照着操作。
5.2 分步执行过程:诊断、配置、切换协议、复爬
第一步,我先执行了三行基础配置:
git config --global http.postBuffer 524288000 git config --global http.lowSpeedLimit 1 git config --global http.lowSpeedTime 999999接着再次克隆,发现断开的频率降低了,但速度依然很慢,Receiving objects从 70% 熬到了 92%,还是断了。这让我意识到,瓶颈不只是客户端超时,仓库本身的传输量太大。
第二步,我改用浅克隆,看看分支主干的体积是否可接受:
git clone --depth 1 --single-branch --branch main https://github.com/user/blog.git结果只用了不到 10 分钟就把最新版本拉下来了。但作为维护者,我不能一直停留在浅克隆状态,后面需要全量历史才能正确生成站点归档。于是我先保留浅仓库作为工作副本,再用git fetch --unshallow补齐历史,这一步虽然慢,但不会中断,因为前期的三行配置保证了“慢但不死”。
第三步,我重新生成了一对 ed25519 key,把 remote 从 HTTPS 切换成 SSH:
git remote set-url origin git@github.com:user/blog.git git push -u origin main之后日常的git push明显比 HTTPS 稳,除非遇到网络完全断开的阶段,几乎不再有推送失败的情况。整套流程下来,我没有使用任何第三方工具或非官方接口,纯粹利用 Git 自身的配置和协议特性,就把这个 2.3GB 仓库的日常维护成本降了下来。
5.3 几条可长期使用的 Git 配置沉淀
如果你也想一劳永逸,可以把下面这段配置写到~/.gitconfig里,作为个人 Git 环境的基础配置:
[user] name = yourname email = youremail [http] postBuffer = 524288000 lowSpeedLimit = 1 lowSpeedTime = 999999 version = HTTP/1.1 [pack] window = 0 [pull] rebase = true我个人的使用经验是:postBuffer和lowSpeedTime组合适合所有使用 HTTPS 的场景;http.version HTTP/1.1只在出现莫名的 curl 56 时才有必要,如果你所在网络环境完全正常,不设也可以。pack.window=0会稍微增加传输包体积,如果你常推大仓库且内存足够,也可以不设置。
最后再分享一个小技巧:如果你要拉取的仓库很大,但只是想看看代码或跑一下编译,先用--depth 1浅克隆,运行没问题后再考虑要不要补全历史。很多“克隆慢”的痛点,其实是“不必要的数据量”放大了“网络波动”的影响。把这两件事分开处理,GitHub 克隆/推送速度慢的问题,大多数时候真的没那么难缠。