Short answer
Work out which connection was reset: an outgoing request to an API or database, or requests into your server. For outgoing calls, retry idempotent requests (GET, and anything with an idempotency key) with a short backoff, and add a timeout. For your own server behind a load balancer or proxy, set server.keepAliveTimeout longer than the proxy’s idle timeout, so Node.js never closes a connection the proxy is about to reuse. Always handle the error event on sockets and streams, or one reset crashes the process.
Why the connection was reset, and the fix
01
The other side closed an idle keep-alive connection
“A connection was forcibly closed by a peer. This normally results from a loss of the connection on the remote socket due to a timeout or reboot.” Node.js, Errors: common system errors (ECONNRESET)
HTTP clients reuse connections. If the server or a proxy drops an idle one at the same moment your app sends a new request on it, the request fails with ECONNRESET. This shows up as rare, random failures; Node.js’s built-in fetch may report it as UND_ERR_SOCKET (other side closed) instead. Retry requests that are safe to repeat, and keep client idle timeouts shorter than the server’s.
async function getWithRetry(url, tries = 3) { for (let i = 1; ; i++) { try { return await fetch(url, { signal: AbortSignal.timeout(10_000) }); } catch (err) { const code = err.cause?.code ?? err.code; if (i >= tries || !['ECONNRESET', 'UND_ERR_SOCKET'].includes(code)) throw err; await new Promise((r) => setTimeout(r, 200 * i)); } } }02
Your server closes connections before the load balancer does
“The number of milliseconds of inactivity a server needs to wait for additional incoming data, after it has finished writing the last response, before a socket will be destroyed.” Node.js, HTTP: server.keepAliveTimeout
Behind AWS ALB, nginx or another proxy, Node.js’s default keep-alive timeout can be shorter than the proxy’s idle timeout. Node.js closes the socket, the proxy sends the next request on it, and the client gets a 502 or a reset. Make the Node.js timeout longer than the proxy’s, and headersTimeout a little longer still.
const server = app.listen(3000); // proxy idle timeout is 60 s (the ALB default) server.keepAliveTimeout = 65_000; server.headersTimeout = 66_000;03
The server crashed, restarted or rejected the request
A reset also happens when the remote process crashes mid-request, a container restarts during a deploy, a request body is larger than the server accepts, or a firewall cuts long connections. Check the other side’s logs at the same timestamp before changing your own code.
04
An unhandled error event crashes the process
A reset on a socket or stream emits an error event. With no listener, Node.js throws it and the whole process exits. Handle it where the stream is created, and log it with the request it belongs to.
const req = https.request(options, onResponse); req.on('error', (err) => { console.error('upstream failed', err.code); }); req.end();
Checked against the vendor’s docs on : Node.js: Errors (common system errors); Node.js: HTTP (server.keepAliveTimeout, server.headersTimeout).
Rather hand it over?
Fixed scope, fixed price, paid upfront. The work happens in writing; no call needed.
- from $450App Fix TicketOne reproducible bug in an existing mobile or web app, diagnosed and fixed.See scope and start
- from $450AI-Built App RescueA Lovable, Bolt or Cursor app checked end to end in 3 days, then its worst problems fixed at a fixed price.No issues worth fixing, no charge. If the Launch Check finds nothing rated medium or higher, Simon refunds the full $450. You keep the report either way. This applies to the first 10 Launch Checks bought after October 30, 2026.See scope and start
Related: ECONNREFUSED: connection refused · Node.js EADDRINUSE: address already in use · “Uncaught (in promise)” errors · “self-signed certificate in certificate chain”