Geonode logo
Library and script errors

How to Fix the "fetch failed" Error in Node.js

Read the cause Node.js attaches, open the guide for it, or route fetch through a proxy in code.

Updated

TL;DR

TypeError: fetch failed means Node.js's built-in fetch() got no HTTP response because the connection, DNS lookup, TLS handshake or proxy tunnel failed, and the underlying error sits in err.cause. Fix the failure that cause names; when requests must use a proxy, route them through undici's ProxyAgent.

What fetch failed looks like in each client

The first line is always the same; the [cause] part names the real failure.

WhereWhat you see
Node.js 20 and 24.11, rejection not caught (line number varies)node:internal/deps/undici/undici:13510 ... Error.captureStackTrace(err); ... TypeError: fetch failed
Node.js 20 and 24, console.error(err), nothing on the portTypeError: fetch failed ... [cause]: Error: connect ECONNREFUSED 127.0.0.1:3000
Node.js 20 and 24, host name not foundTypeError: fetch failed ... [cause]: Error: getaddrinfo ENOTFOUND api.example.invalid
Node.js 20 and 24, server's root CA missing from Node.js's listTypeError: fetch failed ... [cause]: Error: unable to get local issuer certificate
Node.js 24, proxy answered CONNECT with 407TypeError: fetch failed ... [cause]: DOMException [Error]: Request was cancelled. ... cause: RequestAbortedError [AbortError]: Proxy response (407) !== 200 when HTTP Tunneling
Chrome, fetch() in a web page (for comparison)TypeError: Failed to fetch

Why this happens

fetch() gives every network failure the same short message and keeps the details inside.

The Fetch Standard says a network error rejects fetch() with a TypeError, so undici, the HTTP client inside Node.js, keeps the lower-level error on that TypeError's cause property.

Diagnose your fetch failed first

The first check needs only a browser; the others read what Node.js stored with the error.

  • Load the URL in a browser on this computer; if it opens, the site is up and Node.js likely differs in proxy or certificate setup.

  • Print err.cause rather than err.message; a code there such as ECONNREFUSED or ENOTFOUND is the failure fetch wrapped.

  • Compare the host and port in that cause with your proxy URL; if they match, Node.js never reached the proxy itself.

  • Run curl -v with the same HTTPS_PROXY exported; if it prints Uses proxy env variable and succeeds, fetch is skipping that variable.

  • When the inner error says Request was cancelled, check err.cause.cause; Proxy response (407) !== 200 there means the proxy refused the tunnel.

Solutions ranked by effectiveness

Start with the cause; the second and third fixes apply when requests must go through a proxy.

  1. Most common fix

    Match err.cause to its own guide

    Applies when the cause carries a system or TLS code. Each code already has a full guide under Related errors, so open that page rather than changing the fetch call.

    1. ECONNREFUSED: that address and port refused the connection; open the ECONNREFUSED guide.

    2. ENOTFOUND or EAI_AGAIN: the host name did not resolve; open the EAI_AGAIN guide.

    3. Certificate errors: open Unable to verify the first certificate or Self-signed certificate in chain.

    4. UND_ERR_CONNECT_TIMEOUT: no connection within 10 seconds; open the ETIMEDOUT guide.

  2. Check next

    Send fetch through undici's ProxyAgent

    Applies when your network or job requires a proxy, because fetch on its own opens a direct connection. Import fetch and ProxyAgent together from undici@7, which needs Node.js 20.18.1 or later.

    // npm install undici@7
    const { fetch, ProxyAgent } = require("undici");
    
    const proxy = "http://USERNAME:PASSWORD@proxy.geonode.io:9000";
    const dispatcher = new ProxyAgent(proxy);
    
    fetch("https://example.com/", { dispatcher })
      .then((res) => console.log("status", res.status))
      .catch((err) => {
        for (let e = err; e; e = e.cause) console.log(`${e.name}: ${e.message}`);
      });
  3. For developers

    Turn on Node.js's built-in proxy support

    Applies when an SDK or other code you cannot edit calls global fetch. It needs Node.js 24.14+ or 25.4+ (26 has it); earlier versions throw a TypeError instead of skipping it.

    const http = require("node:http");
    
    http.setGlobalProxyFromEnv({
      HTTPS_PROXY: "http://USERNAME:PASSWORD@proxy.geonode.io:9000",
      NO_PROXY: "localhost,127.0.0.1",
    });
    
    fetch("https://example.com/")
      .then((res) => console.log("status", res.status))
      .catch((err) => {
        for (let e = err; e; e = e.cause) console.log(`${e.name}: ${e.message}`);
      });

Stop the fetch failed error from coming back

Habits that keep the real error visible and keep every request on the route you chose.

  1. Store the inner error's code with each failed request, so dashboards count refused ports and DNS misses apart.

  2. Create the ProxyAgent in one shared module and import it everywhere, so no fetch call leaves without it.

  3. Pin undici to major 7 in package.json; undici 8 declares Node.js 22.19 or later.

  4. Send one request through the proxy at startup and exit with its cause if it fails.

  5. In CI, aim the proxy URL at a closed local port; any request that still succeeds is skipping the proxy.

No cap on simultaneous proxy connectionsGeonode's proxy FAQ sets no connection limit, so parallel fetch calls can share one ProxyAgent.
Try residential proxies

Related errors

Learn more

FAQ

It is Node.js's generic message for a request that never got a response from the server, whether a port refused, DNS failed, a certificate was rejected or a proxy tunnel broke; the specific error rides along in err.cause.

Built-in fetch reads HTTP_PROXY, HTTPS_PROXY and NO_PROXY only after you switch that on, for example with NODE_USE_ENV_PROXY=1 at startup on Node.js 24.0+ or 22.21+. Node.js 20 accepts the variable without complaint and still connects directly.

The agent option belongs to node-fetch and Node's http module, and built-in fetch skips it silently. Built-in fetch reads its dispatcher option instead, which takes an object implementing undici's Dispatcher API.

No. Chrome throws TypeError: Failed to fetch for a failed fetch() in a web page, with no cause attached, and for CORS refusals it shows the details only in the developer console.

Chrome follows the computer's proxy settings and certificate stores. Node.js does not: its fetch needs a proxy from code or enabled variables, and official builds trust a bundled Mozilla CA list unless started with --use-system-ca (22.15+ or 23.8+).

No. fetch() resolves normally for a 404, a 500 or any other HTTP status from the server, so check res.ok yourself. fetch failed means no such response came back.

Basic login that ProxyAgent sends

Geonode's username and password login is Basic Authentication; ProxyAgent builds that header from your URL.