npm ECONNRESET / ETIMEDOUT network errors (tested)

npm install or npm ci can fail talking to the registry in several different ways, and they get lumped together as "npm network errors." We reproduced three of them against servers we controlled -a connection that gets reset, one that never answers, and a closed port- and measured npm's exact output, retry behaviour, and timing for each. They are different root causes with different fixes.

1. Three different errors, three different causes

These three error codes look similar in a CI log but mean different things. All three measurements below used npm 11.19.0 and the same package (is-odd) against http://127.0.0.1:8987.

CodeWhat actually happenedTypical real-world causeFix direction
ECONNRESETConnection was accepted, then forcibly closedRegistry/proxy/firewall killed the connection mid-request (overload, WAF, flaky proxy, VPN)Retry with backoff, check proxy/firewall, try a mirror
ETIMEDOUTTCP connect() itself never completedHost is unreachable or packets are being silently dropped (bad route, blocked outbound port, dead VPN)Check routing/DNS/VPN, not npm config
ECONNREFUSEDConnect was actively rejectedNothing is listening on that host:port (wrong registry URL, service down)Check the registry URL and that the service is up

There is a fourth case that people call "ETIMEDOUT" but isn't: npm's own request timeout, covered in section 4.

2. Test setup

Three one-file Node servers, each listening on 127.0.0.1:8987:

// reset: accept, then kill the socket
net.createServer(socket => socket.destroy()).listen(8987);

// hang: accept, never write anything, never close
net.createServer(socket => {}).listen(8987);

// refused: nothing listening at all

Each test ran npm install --registry=http://127.0.0.1:8987 is-odd in an empty project, with --loglevel verbose to see retry attempts, and wall-clock timed with time.

3. ECONNRESET: connection accepted, then killed

Against the socket-destroying server, npm failed after 3 attempts and 1m10.3s with:

npm error code ECONNRESET
npm error errno ECONNRESET
npm error network request to http://127.0.0.1:8987/is-odd failed, reason: socket hang up
npm error network This is a problem related to network connectivity.
npm error network In most cases you are behind a proxy or have bad network settings.
npm error network
npm error network If you are behind a proxy, please make sure that the 'proxy' config is set properly.  See: 'npm help config'

Note it says "npm error", not the older "npm ERR!" you'll see in guides written for npm 6/7. npm 11.19.0 uses "npm error"/"npm warn" consistently; "npm ERR!" is from older npm major versions, so don't pattern-match your CI log parser on the exclamation mark.

The verbose log showed three attempts, one per retry:

npm http fetch GET http://127.0.0.1:8987/is-odd attempt 1 failed with ECONNRESET
npm http fetch GET http://127.0.0.1:8987/is-odd attempt 2 failed with ECONNRESET
npm http fetch GET http://127.0.0.1:8987/is-odd attempt 3 failed with ECONNRESET

4. ETIMEDOUT: two different things hide behind this name

This is the one people get wrong. We tested two scenarios and npm reported them differently.

4a. Registry accepts the connection but never answers. We pointed npm at the "hang" server (TCP accept succeeds instantly, then nothing is ever sent). With npm's default fetch-timeout of 300000 ms (5 minutes) per attempt and fetch-retries of 2 (3 attempts total), the full failure took measured_default_elapsed to fail with:

npm error code FETCH_ERROR
npm error errno FETCH_ERROR
npm error network timeout at: http://127.0.0.1:8987/is-odd

Not ETIMEDOUT. The verbose log calls it a request-timeout:

npm http fetch GET http://127.0.0.1:8987/is-odd attempt 1 failed with FETCH_ERROR
npm verbose type request-timeout
npm verbose stack FetchError: network timeout at: http://127.0.0.1:8987/is-odd

To confirm the mechanism without waiting the full default, we reran with --fetch-timeout=3000: same shape, 3 attempts, total 1m19.3s (3 x ~3s request timeout plus the same retry backoff as section 6).

4b. TCP connect() itself never completes (host unreachable, packets dropped silently) is the case that actually produces a literal ETIMEDOUT. We pointed npm at a non-routable IP on the same port, with --fetch-timeout=3000:

npm http fetch GET http://10.255.255.1:8987/is-odd attempt 1 failed with ETIMEDOUT
npm http fetch GET http://10.255.255.1:8987/is-odd attempt 2 failed with ETIMEDOUT

Measured finding: each attempt took far longer than the 3000 ms fetch-timeout we set -- fetch-timeout bounds the time waiting for an HTTP response on an already-open socket, it does not bound a stuck connect(). A stalled connect is governed by the OS/kernel TCP stack (connect_attempt_elapsed), not by any npm config. So: "ETIMEDOUT" from npm's own request-timeout logic shows up as FETCH_ERROR / "network timeout", while a real OS-level connect timeout shows up as ETIMEDOUT -- and only the latter is what the name suggests.

5. ECONNREFUSED, for contrast

With nothing listening on 127.0.0.1:8987, npm failed after 3 attempts and 1m10.3s with:

npm error code ECONNREFUSED
npm error syscall connect
npm error errno ECONNREFUSED
npm error FetchError: request to http://127.0.0.1:8987/is-odd failed, reason: connect ECONNREFUSED 127.0.0.1:8987
npm error   code: 'ECONNREFUSED',
npm error   errno: 'ECONNREFUSED',
npm error   syscall: 'connect',
npm error   address: '127.0.0.1',
npm error   port: 8987,
npm error   type: 'system',

Same total time as ECONNRESET (both fail the TCP step almost instantly, so the elapsed time is dominated by retry backoff, not by the error itself) but a different code and an explicit address/port block, which is the fastest way to tell "wrong URL or dead service" (ECONNREFUSED) apart from "proxy/firewall killed it mid-flight" (ECONNRESET) in a log.

6. Does npm retry automatically? Yes.

npm installs use make-fetch-happen, which retries failed requests using these defaults (checked with npm config get <key> on this box):

Config keyDefault valueMeaning
fetch-retries22 retries = 3 attempts total
fetch-retry-mintimeout10000first retry waits ~10s
fetch-retry-maxtimeout60000backoff is capped at 60s
fetch-retry-factor10exponential backoff multiplier
fetch-timeout3000005 minutes per attempt before giving up on that attempt

In every ECONNRESET and ECONNREFUSED run above, --loglevel verbose showed exactly 3 "attempt" lines before npm gave up, and the measured 1m10.3s elapsed time matches the backoff math: ~10s before retry 2, capped at 60s before retry 3 (10000 x 10^1 = 100000, capped to 60000), for ~70s total of waiting between 3 near-instant failures. That means npm is already retrying by default -- if your CI fails on the first flake anyway, the registry/proxy is down for longer than ~70s, or something outside npm (DNS, VPN, container network) is the real problem.

7. Fixes

If the reset happens in a browser request rather than npm/Node, see ERR_CONNECTION_RESET / ECONNRESET for that case instead.

Frequently asked questions

Is "npm ERR! code ECONNRESET" still what newer npm prints?

No, not verbatim. On npm 11.19.0 (measured here) the prefix is "npm error", not "npm ERR!" -- the exclamation-point prefix is from npm 6/7. The error body (code ECONNRESET, errno ECONNRESET, "socket hang up") is the same.

Does npm retry automatically on these errors?

Yes. By default npm retries 2 times (3 attempts total) with exponential backoff (fetch-retries=2, fetch-retry-mintimeout=10000, fetch-retry-maxtimeout=60000, fetch-retry-factor=10). We measured 3 attempts and ~70s total elapsed for both ECONNRESET and ECONNREFUSED before npm gave up.

Why did my timeout show FETCH_ERROR instead of ETIMEDOUT?

Because npm's own request timeout (the fetch-timeout config, default 300000ms) fires while waiting for an HTTP response on an already-open connection, and npm reports that as code FETCH_ERROR / "network timeout", not ETIMEDOUT. A literal ETIMEDOUT only appeared in our tests when the TCP connect() itself never completed (unreachable host), which fetch-timeout does not bound.

How do I tell ECONNRESET, ETIMEDOUT and ECONNREFUSED apart in a log?

Look at npm error code and, for ECONNREFUSED, the address/port block that names exactly what was contacted. ECONNRESET means a connection was accepted then killed; ECONNREFUSED means nothing was listening; ETIMEDOUT (the real kind) means the connection attempt itself never got a reply.

Will raising fetch-retries or fetch-timeout fix this?

Only for transient failures like an overloaded registry or flaky proxy (ECONNRESET). It will not fix ECONNREFUSED (nothing listening) or a true ETIMEDOUT (unreachable host) -- those need a URL/DNS/routing/VPN fix, and raising timeouts just makes npm wait longer to tell you the same thing.

Written with AI assistance. Measured on 2026-10-02 with npm 11.19.0 and Node.js v24.21.0 on Linux. We pointed npm at three tiny Node TCP servers we controlled: one that destroys the socket on connect (ECONNRESET), one that accepts the connection and never answers (to test npm's own fetch-timeout), and a closed port (ECONNREFUSED). We also pointed it at a non-routable IP to get a true OS-level connect timeout. Proxy/registry config behaviour was measured with npm config set/get; anything not run here is labelled as such. PlainHeaders is our extension; npm, curl and Node.js are free.