Node fetch ECONNRESET every few thousand requests: the keep-alive socket the server already closed
Problem
A Node 20 worker calling one upstream API a few hundred thousand times a day: every few thousand requests, fetch dies with
TypeError: fetch failed
at Object.fetch (node:internal/deps/undici/undici:11576:11)
cause: Error: read ECONNRESET
at TCP.onStreamRead (node:internal/stream_base_commons:217:27)It is load-correlated — more traffic, more resets — and retrying by hand always succeeds, which smells like a bug in our retry logic. It was not.
Root cause
Node's global fetch (undici) pools connections with keep-alive. The upstream (or an idle-timeout load balancer in front of it) closes idle sockets after N seconds, but undici's pool considers a socket usable until the close arrives. Your next request is written onto a socket the server is tearing down: ECONNRESET. The race widens exactly when traffic spikes, because a busy event loop delays processing the FIN that would have marked the socket dead.
Node has known about this class since forever — the classic fix for http.Agent was keepAliveMsecs and retry-on-idle-socket. Undici exposes the same escape hatch.
import { Agent, RetryAgent, setGlobalDispatcher } from 'undici';
const upstream = new Agent({
… 11 more lines in the fix🔒 the fix — including 2 code blocks — is members-only. $1/mo unlocks everything.