ERR_EMPTY_RESPONSE / curl (52) Empty reply (tested)

net::ERR_EMPTY_RESPONSE in Chrome and curl: (52) Empty reply from server are the same event. The TCP connection opened, the request went out, and the server closed the connection without sending a single byte. We built eight server faults and recorded the exact error from Chromium, curl, Node fetch and Python for each one.

1. What the error means

The connection worked. The server accepted it and read the request. Then it closed the socket cleanly (a TCP FIN) before writing a status line. No status code exists, so there is nothing to show.

That rules out some things. If nothing listens on the port, you get ERR_CONNECTION_REFUSED. If the server closes with a TCP RST instead of a FIN, you get ERR_CONNECTION_RESET. If the server sends headers and then dies, you get a truncated-body error instead. We measured all three below.

2. Same event, four error messages

ClientExact error we saw
Chromium 153net::ERR_EMPTY_RESPONSE; page fetch() gives TypeError: Failed to fetch
curl 8.14.1curl: (52) Empty reply from server, exit 52, %{http_code} = 000
Node 24 fetchTypeError: fetch failed, cause.code = UND_ERR_SOCKET, cause message "other side closed"
Python urllibhttp.client.RemoteDisconnected: Remote end closed connection without response

In Node, "other side closed" with UND_ERR_SOCKET is the empty-reply signal. Many people search for socket hang up; that is what Node's older http.get API reported for the same server fault in our test (err.code ECONNRESET, message socket hang up), even though no TCP reset happened.

3. Results: cause by cause

Server faultChromiumcurl exitNode fetch cause.codePython
req.socket.end() in handler, nothing writtenERR_EMPTY_RESPONSE52UND_ERR_SOCKETRemoteDisconnected
req.socket.destroy() in handlerERR_EMPTY_RESPONSE52UND_ERR_SOCKETRemoteDisconnected
process.exit(1) in handlerERR_EMPTY_RESPONSE52UND_ERR_SOCKETRemoteDisconnected
Uncaught exception thrown in a timer (process crash)ERR_EMPTY_RESPONSE52UND_ERR_SOCKETRemoteDisconnected
Express: res.write() then next(err) in the same tickERR_EMPTY_RESPONSE52UND_ERR_SOCKETRemoteDisconnected
server.timeout = 1000, handler takes 3 sERR_EMPTY_RESPONSE52 (after ~1 s)UND_ERR_SOCKETRemoteDisconnected (after ~1 s)
http:// sent to an HTTPS (TLS) portERR_EMPTY_RESPONSE52UND_ERR_SOCKETRemoteDisconnected
Look-alikes that are not empty replies
req.socket.resetAndDestroy() (TCP RST)ERR_CONNECTION_RESET56 "Recv failure: Connection reset by peer"ECONNRESETConnectionResetError 104
Express: res.flushHeaders() then next(err)200, then ERR_INCOMPLETE_CHUNKED_ENCODING18 "transfer closed with outstanding read data remaining"200, then body read throws TypeError: terminatedIncompleteRead(0 bytes read)
20 KB request header (Node limit is 16 KB)431 with empty body0, status 431status 431HTTPError 431
Express: error before anything is sentNormal 500 page. Express handles it.

The server log showed one request per client try. Chromium did not retry any of these on a fresh connection.

4. The surprising one: Express error after res.write()

If headers are already sent, Express cannot send a 500, so its final handler destroys the socket. We expected curl to get the headers and a truncated body. It did not. It got an empty reply:

app.get('/x', (req, res, next) => {
  res.write('partial');          // res.headersSent is now true
  next(new Error('boom'));       // same tick
});

$ curl -sS http://127.0.0.1:8992/x
curl: (52) Empty reply from server

res.headersSent was true, but the bytes were still in Node's write buffer. Destroying the socket in the same tick threw them away. When we called next(err) one tick later (setImmediate), curl received the 200 headers and partial, then failed with exit 18. Either way the client gets a broken response. The fix is to not start writing until you know the response will succeed. See ERR_HTTP_HEADERS_SENT for the related "headers already sent" bug.

5. http:// to an HTTPS port

We sent plain HTTP to a Node https server. Every client reported an empty reply. The server logged:

tlsClientError ERR_SSL_HTTP_REQUEST ... SSL routines:tls_validate_record_header:http request

So if a URL gives ERR_EMPTY_RESPONSE, try https:// on the same port. The opposite mistake (https:// to a plain HTTP port) gives ERR_SSL_PROTOCOL_ERROR instead.

6. Timeouts that close silently

Node's server.timeout is 0 (off) by default in Node 24. When we set it to 1000 ms, a 3 s handler produced an empty reply after about 1 s in every client. The handler later found res.destroyed was true. Node 24 defaults we read from a fresh server: requestTimeout 300000 ms, headersTimeout 60000 ms, keepAliveTimeout 5000 ms, maxHeaderSize 16384 bytes.

Other layers do the same: a load balancer, a proxy or a platform (for example a serverless function hitting its time limit) may close the connection without a response. Not measured here: a proxy reusing a keep-alive connection that the backend just closed can also surface as an empty reply. Keep the backend keep-alive timeout longer than the proxy's.

7. Header too large is not an empty reply (in Node)

A 20 KB header got a real 431 Request Header Fields Too Large with Connection: close from Node 24, in all four clients. Chromium showed the 431 with an empty body, not ERR_EMPTY_RESPONSE. Other servers differ; see 431 Request Header Fields Too Large and Request Header Or Cookie Too Large.

8. How to fix it

Once the server responds, check the status and headers it sends with our HTTP header checker.

Frequently asked questions

What does ERR_EMPTY_RESPONSE mean?

The server accepted the connection and then closed it without sending any bytes. There is no status code. In our tests curl reported this as exit 52, "Empty reply from server".

Is ERR_EMPTY_RESPONSE the same as curl error 52?

Yes. In every case we built, Chromium 153 showed net::ERR_EMPTY_RESPONSE exactly when curl 8.14.1 exited with 52. Node fetch showed UND_ERR_SOCKET "other side closed" and Python showed RemoteDisconnected.

How is it different from ERR_CONNECTION_RESET?

A clean close (FIN) with no data gives ERR_EMPTY_RESPONSE and curl 52. A reset (RST) gives ERR_CONNECTION_RESET, curl 56 and Node ECONNRESET. We produced the reset with socket.resetAndDestroy().

Why do I get ERR_EMPTY_RESPONSE on localhost?

The most common causes we reproduced: the server process crashed in the handler, the code closed the socket, a server timeout fired, or you used http:// on a port that speaks HTTPS.

Does a too-large header cause an empty reply?

Not in Node 24. A 20 KB header got a proper 431 response with Connection: close in Chromium, curl, Node fetch and Python. Other servers may behave differently.

Written with AI assistance. Measured on 2026-10-02 with headless Chromium 153.0.8010.12 (Playwright), curl 8.14.1, Node.js 24.21.0 fetch and Python 3.13.5 urllib, against Node 24 and Express 5.2.1 test servers on 127.0.0.1. Proxy keep-alive races are from general docs, not measured. PlainHeaders is our extension; curl, Python, Node.js and Chromium are free.