TypeError: fetch failed
Node.js reports every network failure in fetch() with the same two words. The
real reason is one level down, in error.cause.
In short: Node’s built-in fetch (undici) throws
TypeError: fetch failed when no HTTP response arrives: the name did not
resolve, the connection was refused or timed out, a proxy was needed, or the certificate
was not trusted. Print error.cause, find its code below, and fix that layer.
A reply with status 401 or 429 is a different problem.
Last updated
Is it the host or your side?
Check the host from NetOkay
The website check requests the URL from NetOkay’s Worker. If NetOkay gets an HTTP response, the host is up and the failure is in the network path or settings of the machine that runs your code.
Check the host from NetOkay →1. Print the real error
The message is always the same; the cause carries the system error code. Log it where you call fetch:
try {
const res = await fetch(url);
} catch (err) {
console.error(err.cause ?? err);
}
When the error comes from a tool you do not control, such as an editor extension, a build step or an AI agent, repeat the request with the same Node binary in the same shell. Replace the URL with the one that fails:
node -e "fetch(process.argv[1]).then(r => console.log(r.status), e => console.error(e.cause ?? e))" https://api.example.com/
A status number means fetch worked from this shell, so compare it with the environment the tool runs in. An error object gives you the code to look up next.
2. Match the cause code
-
ENOTFOUNDorEAI_AGAIN(fromgetaddrinfo): the host name did not resolve. Check for a typo, then runnslookup api.example.comon the same machine. A VPN, a corporate resolver or a container without DNS can fail while public DNS answers; compare with the DNS lookup. -
ECONNREFUSED: the address answered, but nothing accepts connections on that port. The server is down, the port is wrong, orlocalhostmeans something else: inside a container it is the container itself, and Node may try::1before127.0.0.1while the server listens on IPv4 only. Use127.0.0.1or make the server listen on both. -
UND_ERR_CONNECT_TIMEOUT,ETIMEDOUTorENETUNREACH: no connection within undici’s 10-second connect timeout. A firewall drops the traffic, the network only reaches the internet through a proxy (step 3), or an IPv6 route is broken. Node 20 and later try IPv6 and IPv4 addresses in turn; anAggregateErrorin the cause lists each attempt. -
ECONNRESETorUND_ERR_SOCKET(“other side closed”): the connection opened and was then cut. A proxy or firewall that inspects TLS, a server that closes idle keep-alive connections, or an upstream that crashed mid-request. If it happens only on the first request after a pause, retry once on this code. -
UND_ERR_HEADERS_TIMEOUTorUND_ERR_BODY_TIMEOUT: the server accepted the request but sent no headers, or stopped sending the body, for 300 seconds. Slow AI model responses and long exports hit this; raise the limits in step 5 or stream the response. -
UNABLE_TO_GET_ISSUER_CERT_LOCALLY,SELF_SIGNED_CERT_IN_CHAINorUNABLE_TO_VERIFY_LEAF_SIGNATURE: Node does not trust the certificate it was shown. Usually a company proxy re-signs TLS traffic with its own root certificate; see step 4. -
CERT_HAS_EXPIREDorERR_TLS_CERT_ALTNAME_INVALID: the server’s own certificate is expired or issued for another name. Fix it on the server, or check that you are calling the right host.
3. curl works, Node fetch fails: the proxy
curl reads HTTPS_PROXY and HTTP_PROXY. Node’s built-in fetch
ignores them unless you opt in, so on a network that only allows traffic through a proxy,
curl succeeds and fetch times out. Node 24.5 and later, and 22.21 and later, honour the
variables when NODE_USE_ENV_PROXY=1 is set or Node starts with
--use-env-proxy:
NODE_USE_ENV_PROXY=1 HTTPS_PROXY=http://proxy.internal:3128 node app.js
On older versions, install undici and set a proxy-aware dispatcher once at
start-up:
import { EnvHttpProxyAgent, setGlobalDispatcher } from 'undici';
setGlobalDispatcher(new EnvHttpProxyAgent());
Put internal hosts in NO_PROXY so they do not go through the proxy. Use only
a proxy your network requires and you are allowed to use. For more on why a working curl
does not prove your application’s path, read
browser or curl works, your app fails.
4. Certificate errors behind a company proxy
Node trusts its own bundled list of root certificates, not the one your operating system or browser uses. Give it your organisation’s root certificate in PEM format:
NODE_EXTRA_CA_CERTS=/path/to/company-root.pem node app.js
Node reads NODE_EXTRA_CA_CERTS only when the process starts, so setting it
from inside the application has no effect. On Node 23.8 and later,
node --use-system-ca trusts the operating system’s store instead; Node 24.6
and later, and 22.19 and later, also accept NODE_USE_SYSTEM_CA=1.
Do not set NODE_TLS_REJECT_UNAUTHORIZED=0. It turns off certificate checks
for every connection the process makes, not just the failing one.
5. Requests that take longer than the defaults
undici gives up after 10 seconds without a connection, and after 300 seconds without response headers or between body chunks. For a slow upstream you trust, pass a dispatcher with longer limits:
import { Agent } from 'undici';
const slow = new Agent({
connect: { timeout: 30_000 },
headersTimeout: 600_000,
bodyTimeout: 600_000,
});
const res = await fetch(url, { dispatcher: slow });
A deadline you set yourself with AbortSignal.timeout() fails differently: it
throws a TimeoutError, not fetch failed.
6. Messages that look similar but are not this error
-
“fetch failed with status 401” or “with status 429”. Node’s fetch does
not throw on HTTP error statuses; a library or tool wrote that message after a response
arrived. 401 means the credentials were missing or rejected; 429 means a rate limit, so
wait as long as the
Retry-Afterheader says. - “TypeError: Failed to fetch” in a browser. That is the browser’s message, with other causes: CORS, mixed HTTP and HTTPS content, an extension blocking the request, or no network. Open the Network panel in developer tools to see which.
-
It fails only inside an editor or desktop app. Apps started from the
dock or start menu often do not see variables exported in your shell profile, so
HTTPS_PROXYorNODE_EXTRA_CA_CERTSmay be missing there. Set them where the app reads its environment, or start it from that shell.
7. Record what the failing environment sees
The NetOkay CLI runs on Node in the same shell and records which stage of a request to a
public URL fails (DNS, connection, TLS trust or HTTP), and whether proxy variables and
NODE_EXTRA_CA_CERTS are set there. It uses its own HTTPS client, so it shows
the state of that environment rather than replaying your fetch call. See
CLI: HTTP target.
Calling an AI provider? The AI API status pages show whether its API host answered NetOkay’s probes from several regions in the last few minutes.