ECONNRESET means the other end (or a middlebox) closed the TCP connection abruptly. In Node it's normal noise at scale unless it escapes your handler — then it's a crash.
An 'error' event with no listener throws. http.request, net sockets, and third-party clients must have error handlers attached — .on('error') or try/catch around await.
Load balancers kill idle keep-alive connections (often ~60s). A reused dead socket triggers ECONNRESET on the next write. Solutions: shorter free-timeout than the LB's, or retry idempotent requests.
Upstream OOM-kill or request-size limits can abort connections instead of responding. Correlate timestamps with upstream logs.
process.on('uncaughtException', (e) => log.fatal({e})); process.on('unhandledRejection', (e) => log.fatal({e}));
const req = http.get(url, (res) => {...); req.on('error', (e) => /* classify + retry if idempotent */); req.setTimeout(5000, () => req.destroy());
const agent = new http.Agent({ keepAlive: true, freeTimeout: 30_000 }); // or keepAliveMsecs + maxSockets on https.Agent
# wrap fetches in a retry helper: on 'ECONNRESET' && method is GET → retry with backoff; never blind-retry POSTs
undici/fetch users: set a dispatcher with keepAliveTimeout lower than your LB's idle timeout to avoid reuse-of-dead-socket resets. In clusters of pods behind a service mesh, check sidecar/log shipping overhead too — resets often correlate with proxy resource limits.
No — remote closes are a fact of network life. The bug is letting the error crash your process. Handle socket/request 'error' events and decide per-call whether to retry.
Load multiplies the odds: more keep-alive reuse (dead-socket hits), upstream queues rejecting (RST instead of 503), and LB idle reaping all scale with concurrency. Fix handlers first, then tune agents.
Our most-documented failures, packaged as ready-to-ship starter kits: Docker, Kubernetes, and Terraform.
Browse the template store →One-time. Yours to modify. Instant download from the NinjaOps template store.