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 wireExact error git printed
Server sends a TCP RST while git is sending/receiving the RPC bodyerror: RPC failed; curl 56 Recv failure: Connection reset by peer
Server closes gracefully (FIN) before sending any response headerserror: RPC failed; curl 52 Empty reply from server
Server closes gracefully (FIN) partway through a response body it had already started sendingerror: RPC failed; curl 18 transfer closed with outstanding read data remaining
Server never responds at all (hang), with a client-side low-speed timeout configurederror: 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:

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.postBufferRequest 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 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.