TypeError: fetch failed (Node 18+ / undici)

Node’s built-in fetch threw before it got an HTTP status. The real reason is almost always on error.cause — refused, DNS, TLS, or a connect timeout — not the outer TypeError message alone.

How it shows up

TypeError: fetch failed
    at node:internal/deps/undici/undici:…

# Often with a nested cause:
[cause]: Error: connect ECONNREFUSED 127.0.0.1:3000
[cause]: Error: getaddrinfo ENOTFOUND api.example.invalid
[cause]: ConnectTimeoutError: Connect Timeout Error (UND_ERR_CONNECT_TIMEOUT)
[cause]: Error: certificate has expired
[cause]: Error: self-signed certificate in certificate chain

You never see an HTTP status from the target — TCP/TLS never finished cleanly. Logging only err.message (“fetch failed”) hides the useful bit.

Node fetch vs browser Failed to fetch

SurfaceMeans
Node: TypeError: fetch failedundici could not complete the request; dig into error.cause
Browser: TypeError: Failed to fetchOpaque client failure — often CORS, mixed content, adblock, or a network error the page is not allowed to name
CORS errorsBrowser-only policy; Node fetch does not enforce CORS

In Node there is no CORS. If the same URL works in curl but fails in Node, compare TLS trust stores, DNS, IPv6, and whether you meant a different host/port.

Read the cause chain

try {
  const res = await fetch("https://api.example.com/v1/health");
  console.log(res.status);
} catch (err) {
  console.error(err.message);          // "fetch failed"
  console.error(err.cause);            // the real Error
  console.error(err.cause?.code);      // ECONNREFUSED, ENOTFOUND, …
  console.error(err.cause?.cause);     // sometimes nested further
}

Cause cheat sheet

cause / codeLikely meaningSee also
ECONNREFUSEDNothing accepted the TCP connection (wrong host/port, process down, IPv6 ::1 vs 127.0.0.1)ECONNREFUSED ::1, localhost refused
ENOTFOUND / getaddrinfoDNS name does not resolvegetaddrinfo ENOTFOUND, ERR_NAME_NOT_RESOLVED
UND_ERR_CONNECT_TIMEOUT / ConnectTimeoutErrorTCP connect hung until undici gave upETIMEDOUT, ERR_CONNECTION_TIMED_OUT
CERT_HAS_EXPIRED / certificate has expiredPeer cert dates rejectedcertificate has expired
self-signed / UNABLE_TO_VERIFY / SELF_SIGNED_CERT_IN_CHAINChain not trusted (dev cert, corporate proxy CA)self-signed in chain, unable to get local issuer
ECONNRESETPeer closed the socket mid-flightECONNRESET

Diagnose with curl alongside Node

# Same URL Node is calling
curl -vI "https://api.example.com/v1/health"
# Force IPv4 if you suspect ::1 / AAAA issues
curl -4 -vI "http://localhost:3000/health"
# See what Node resolves
node -e 'require("dns").lookup("api.example.com", console.log)'

If curl gets a status and Node does not, compare proxies (HTTP_PROXY/HTTPS_PROXY), custom agents, and whether the app rewrites the URL. Paste mystery tokens from Authorization headers into JWT decoder only on copies you are allowed to inspect — never log live secrets.

Fix checklist

  1. Log err.cause (and cause.code) once — stop debugging the outer “fetch failed” string alone.
  2. Confirm host + port + scheme — typo’d env URL, http vs https, container name only reachable on the Docker network.
  3. Refused — start the upstream, fix the port, try 127.0.0.1 vs localhost (IPv6).
  4. DNS — fix the name, search domain, or cluster DNS; watch for leftover .local / staging hosts.
  5. Timeout — raise undici connect timeout only after checking firewall / security group / wrong VPC; a longer timeout does not fix a black hole.
  6. TLS — renew the cert, install the corporate root via NODE_EXTRA_CA_CERTS, never leave NODE_TLS_REJECT_UNAUTHORIZED=0 in production.
  7. Not CORS — if the failure is only in the browser, switch to the Failed to fetch / CORS guides.

Related

TypeError: Failed to fetch (browser) · CORS errors explained · ECONNREFUSED ::1 · ETIMEDOUT · net::ERR_CONNECTION_TIMED_OUT · ERR_NAME_NOT_RESOLVED · getaddrinfo ENOTFOUND · certificate has expired · self-signed certificate in certificate chain · localhost refused to connect · Anvil HTTP status · Anvil URL inspect

Questions

What does TypeError: fetch failed mean in Node?

Node’s undici fetch could not complete the HTTP request. The outer message is generic; the real reason is on error.cause (refused, DNS, TLS, timeout, reset).

How is this different from TypeError: Failed to fetch in the browser?

The browser message often hides CORS or mixed content. Node does not enforce CORS; dig into error.cause for the network/TLS code instead.

How do I see the real error?

Log err.cause and err.cause.code. You should see codes like ECONNREFUSED, ENOTFOUND, CERT_HAS_EXPIRED, or UND_ERR_CONNECT_TIMEOUT.

What causes ECONNREFUSED under fetch failed?

Nothing accepted TCP on that host:port — service down, wrong port, or localhost resolving to ::1 while the app listens only on 127.0.0.1.

What is UND_ERR_CONNECT_TIMEOUT?

undici’s connect timeout: the TCP handshake never finished in time. Check firewalls, routing, and that the host is the one you think.

Can a bad certificate cause fetch failed?

Yes. Expired, self-signed, or incomplete chains appear as nested TLS errors under TypeError: fetch failed.

Does NODE_TLS_REJECT_UNAUTHORIZED=0 fix it safely?

No. It skips trust checks and is unsafe for production. Use a real cert or NODE_EXTRA_CA_CERTS for a private CA.

Published .

Try it

Related guides

Newest guides