git RPC failed; curl 56 Connection reset by peer (tested)
error: RPC failed; curl 56 Recv failure: Connection reset by peer and fatal: the remote end hung up unexpectedly happen when a git push, clone or pull over HTTP(S) loses its TCP connection partway through. We built a local git smart-HTTP server, forced four different kinds of connection failure against it, and recorded the exact error text each one produces.
Short version: curl's error number tells you how the connection died, not why. A reverse proxy, firewall, antivirus, VPN or flaky Wi-Fi can all cause it; the fix is almost always on the network path between you and the server, not in git itself.
1. The error, and the four things we tested
The message is really two lines:
error: RPC failed; curl 56 Recv failure: Connection reset by peer
fatal: the remote end hung up unexpectedly
"RPC" here means the smart-HTTP exchange git uses for push/clone/fetch (a POST to /git-upload-pack or /git-receive-pack). "curl 56" is libcurl's own error number (CURLE_RECV_ERROR): a read on the socket failed. The exact wording depends on how the connection died. We reproduced four distinct ways and recorded the exact text for each:
| What happened on the wire | Exact error git printed |
|---|---|
| Server sends a TCP RST while git is sending/receiving the RPC body | error: RPC failed; curl 56 Recv failure: Connection reset by peer |
| Server closes gracefully (FIN) before sending any response headers | error: RPC failed; curl 52 Empty reply from server |
| Server closes gracefully (FIN) partway through a response body it had already started sending | error: RPC failed; curl 18 transfer closed with outstanding read data remaining |
| Server never responds at all (hang), with a client-side low-speed timeout configured | error: RPC failed; curl 28 Operation too slow. Less than 1000 bytes/sec transferred the last 3 seconds |
In every case git also printed fatal: the remote end hung up unexpectedly as a second line, except the timeout case. So that second line alone does not tell you which of these happened; the curl number does.
2. Test setup
We ran git http-backend (git's own CGI smart-HTTP implementation) behind a ~130-line Node.js proxy on 127.0.0.1:8984, serving real bare repos with a 5 MB file committed. For the failure cases, the proxy intercepted the POST to git-upload-pack/git-receive-pack and either:
- called Node's
socket.resetAndDestroy()(sends an actual TCP RST, available since Node 18.3), or - called
socket.end()(a graceful FIN, no RST), or - did nothing at all (simulating a server that hangs).
A plain, non-broken clone against the same setup worked end to end, so the failures below are specifically caused by the connection, not a misconfigured test server.
3. Reproducing curl 56 (the RST case)
We read the client's request body for the upload-pack negotiation (177 bytes) then called resetAndDestroy() before writing any response. Full output:
$ git clone http://127.0.0.1:8984/repo.git dest
Cloning into 'dest'...
error: RPC failed; curl 56 Recv failure: Connection reset by peer
fatal: the remote end hung up unexpectedly
With GIT_CURL_VERBOSE=1 set, the verbose trace shows git/curl retrying once by itself before giving up:
== Info: Recv failure: Connection reset by peer
== Info: Connection died, retrying a fresh connect (retry count: 1)
== Info: shutting down connection #0
== Info: Issue another request to this URL: '.../git-upload-pack'
...
== Info: upload completely sent off: 177 bytes
== Info: Recv failure: Connection reset by peer
error: RPC failed; curl 56 Recv failure: Connection reset by peer
So a single RST does not immediately fail the clone: git transparently retries the whole RPC once over a fresh connection. Only the second failure is fatal. This matches what admins observe: an intermittently flaky proxy can make pushes "usually" succeed on retry and fail outright only when it drops two connections in a row.
4. A truncated response looks different again
We let the real pack data start streaming to the client (over 24 KB written), then cut the connection mid-stream. With a hard RST:
error: RPC failed; curl 56 Recv failure: Connection reset by peer
fetch-pack: unexpected disconnect while reading sideband packet
fatal: protocol error: bad pack header
With a graceful FIN instead of an RST, at the same point in the stream:
error: RPC failed; curl 18 transfer closed with outstanding read data remaining
fetch-pack: unexpected disconnect while reading sideband packet
fatal: protocol error: bad pack header
Both add fetch-pack: unexpected disconnect while reading sideband packet and fatal: protocol error: bad pack header — lines that did not appear in section 3, where the connection died before any pack bytes arrived. If you see those two extra lines, the connection broke during the pack transfer, not during the initial request. (Per git's documentation, a truncated pack can also be reported as fatal: early EOF / fatal: index-pack failed in other code paths, e.g. on the receiving end of a push; we did not reproduce that exact string, only the sideband/bad-pack-header variant above.)
5. A hang is silent until something times out
We also tested a server that accepts the connection and then sends nothing back, ever. With no timeout configured, git clone against it simply hung — timeout 6 git clone ... had to kill it after 6 seconds, and git itself printed no error at all. Git has no built-in RPC timeout.
Setting a client-side low-speed timeout changes that:
$ git -c http.lowSpeedLimit=1000 -c http.lowSpeedTime=3 clone http://127.0.0.1:8984/repo.git dest
Cloning into 'dest'...
error: RPC failed; curl 28 Operation too slow. Less than 1000 bytes/sec transferred the last 3 seconds
fatal: the remote end hung up unexpectedly
This took almost exactly 3 seconds (we measured 4.0 s wall clock) — the http.lowSpeedTime value. Without http.lowSpeedLimit/http.lowSpeedTime set, a genuinely stuck connection can hang until the OS's own TCP keepalive/timeout kicks in, which on Linux defaults to much longer than a few seconds. If your pushes "just hang" rather than erroring, this is why, and setting these two config values turns a silent hang into a clear curl 28 error.
6. http.postBuffer: what it actually changes
git config --get http.postBuffer prints nothing by default (unset); per git's docs the built-in default is 1 MiB. We verified what that default actually does by pushing a ~5 MB pack and inspecting the real request headers with GIT_CURL_VERBOSE=1:
| http.postBuffer | Request header we captured |
|---|---|
| default (unset, 1 MiB) | Transfer-Encoding: chunked (no Content-Length) |
| 20971520 (20 MB) | Content-Length: 5138338 (no chunked encoding) |
$ git -c http.postBuffer=20971520 push origin master
...
=> Send header: POST /repo.git/git-receive-pack HTTP/1.1
=> Send header: Content-Length: 5138338
So raising http.postBuffer above the size of your push does exactly one thing: it switches the request from chunked transfer encoding to a single buffered request with a normal Content-Length. It does not make git retry more, send faster, or change anything about how RSTs or timeouts are handled. It only helps if the actual problem is a proxy, antivirus or corporate firewall that mishandles or strips chunked HTTP/1.1 requests — which, per git's own documentation, is the only scenario it's meant for. If your RST or timeout happens on a plain unchunked request too, raising postBuffer will not fix it.
7. What to actually do about it
- If it fails consistently on one large push/clone, rarely on small ones: something in the path (corporate proxy, antivirus, old load balancer) is dropping long-lived or chunked HTTP connections. Try
git config http.postBuffer 157286400(150 MB) to force a single buffered request, per git's docs. If that fixes it, the chunked-encoding path was the problem. - If it fails intermittently, including on small pushes: that's a flaky link, not an encoding issue. Git already retries once by itself (section 3); if that's not enough, shallow-clone (
--depth 1) or split a huge push into smaller ones so each attempt has less to lose. - If it looks like a silent hang rather than an error: set
git config --global http.lowSpeedLimit 1000andhttp.lowSpeedTime 30so a dead connection surfaces as curl 28 within 30 seconds instead of hanging indefinitely (section 5). - If you see "unexpected disconnect while reading sideband packet" / "bad pack header": the connection broke mid-transfer, not at the start (section 4). Check the server/proxy's own timeout settings for long-running requests — many reverse proxies cap request duration independent of data size.
- Use
GIT_CURL_VERBOSE=1 git ...to see exactly which curl error number you get (56, 52, 18, 28, or something else) and whether it happens before or after the pack data starts. That number, per section 1's table, tells you which of these four situations you're actually in.
If the server itself is returning a slow or failing HTTP response rather than dropping the connection, check what it actually sends with our HTTP headers tool. For plain connection resets outside of git specifically, see ERR_CONNECTION_RESET / ECONNRESET; for a server-side timeout to an upstream, see 504 Gateway Timeout.
Frequently asked questions
Does raising http.postBuffer fix curl 56?
Only sometimes. We measured that it changes one thing: requests under the new limit are sent with a plain Content-Length instead of Transfer-Encoding: chunked. That helps only if a proxy or antivirus on the path is mishandling chunked requests. If the reset happens on small, unchunked requests too, postBuffer will not change anything.
Does git retry automatically after a connection reset?
Yes, once. Our GIT_CURL_VERBOSE=1 trace showed git/curl detect the reset, log "Connection died, retrying a fresh connect", and reissue the exact same RPC over a new connection. The RPC failed error we see is from the second, final attempt.
Why do I sometimes see curl 56 and sometimes curl 18 or curl 52 for what feels like the same problem?
The number reflects how the connection died, which we reproduced directly: curl 56 is a hard reset (RST), curl 52 is a graceful close before any response bytes arrive, and curl 18 is a graceful close partway through a response body that had already started. Same underlying flaky link, different failure point.
Why did my clone just hang instead of showing an error?
Git has no built-in timeout for the RPC call. We confirmed a server that accepts the connection and sends nothing back makes git clone hang indefinitely with no error text, until something else (your terminal, a wrapper script, or the OS) kills it. Set http.lowSpeedLimit and http.lowSpeedTime so git gives up on its own with a curl 28 error.
Is this a problem with my repository or my git version?
Almost never. We reproduced the exact same error strings with a perfectly healthy repo and a healthy git 2.47.3 client; the only variable was how we killed the TCP connection on the server side. Corrupt repos produce different errors (e.g. fsck failures), not curl 56.
Written with AI assistance. Measured on 2026-10-02 with git 2.47.3 and Node.js 24.21.0 on Linux. We ran a real git smart-HTTP server (git http-backend as CGI behind a small Node proxy) on 127.0.0.1:8984 and pointed git clone/push at it, including versions that abort the TCP connection mid-request with a real RST (Node's socket.resetAndDestroy()) or a graceful FIN. All error strings below are copy-pasted from real git output. The literal phrase "fatal: early EOF" is from git's source/docs, not reproduced verbatim; our mid-response-truncation test produced a different, also-real message instead (section 4). PlainHeaders is our extension; git, curl and Node.js are free.