denoland/deno · error · TypeError

fetch failed

Error message

fetch failed

What it means

fetch() throws a bare TypeError 'fetch failed' for any transport-level failure: DNS resolution errors, refused connections, TLS handshake problems, proxy failures, or resets before a response was received. Following Node/undici's shape, the human-readable detail is not in the message but in error.cause, which carries the underlying reqwest transport error text.

Solutions

  1. Read err.cause.message first — it contains the actual transport reason (dns error, tcp connect error, invalid certificate, ...)
  2. Connection refused: verify host/port and that the server listens on the right interface
  3. TLS problems: fix the certificate chain or pass the CA with deno run --cert=ca.pem
  4. Proxy problems: correct or unset HTTP_PROXY/HTTPS_PROXY/NO_PROXY for the target host
  5. DNS problems: test with Deno.resolveDns or curl from the same environment
  6. For flaky networks, add retry with backoff for idempotent (GET) requests

Example fix

// before
const res = await fetch(url); // TypeError: fetch failed

// after
try {
  const res = await fetch(url);
} catch (err) {
  if (err instanceof TypeError && err.message === "fetch failed") {
    console.error("transport failure:", err.cause?.message);
  }
  throw err;
}
Defensive patterns

Strategy: try-catch

Try / catch

try {
  const res = await fetch(url, init);
} catch (err) {
  if (err instanceof TypeError && err.message === "fetch failed") {
    const detail = err.cause?.message ?? "";
    if (/refused|timed out|reset/i.test(detail)) {
      // transient: retry with backoff (idempotent requests only)
    } else if (/dns/i.test(detail)) {
      // configuration: bad hostname
    } else if (/certificate|tls|ssl/i.test(detail)) {
      // configuration: trust store / --cert
    }
  } else throw err;
}

Prevention

When it happens

Trigger: fetch('http://localhost:9999/') with nothing listening (connection refused); unresolvable hostname (DNS error); self-signed/expired/mismatched certificate (TLS error); connecting to an https server over plain http or vice versa; HTTP_PROXY/HTTPS_PROXY pointing at a dead proxy; blocked network path (firewall, dropped packets).

Common situations: Integration tests hitting a service that is not started or crashed; Docker/Kubernetes network policies blocking egress; corporate proxy env vars misconfigured; expired TLS certs in CI; DNS failing inside containers; IPv6-only endpoints unreachable from the host.

Related errors


AI-assisted analysis of denoland/deno@a961cdec3b (2026-08-20). Data as JSON: /api/errors/01b00d8d8b5a1b1f. Report an issue: GitHub.

Appendix: source

Thrown at ext/fetch/26_fetch.js:596

    if (cancelHandleRid !== null) {
      core.tryClose(cancelHandleRid);
    }
  }
  // Re-throw any body errors
  if (resp.error !== null) {
    if (inspectorRequestId !== null) {
      safeEmit(inspectorNetwork.loadingFailed, {
        requestId: inspectorRequestId,
        timestamp: DateNow() / 1000,
        type: "Fetch",
        errorText: resp.error[0],
      });
    }
    // `resp.error` is `[detail, cause]`, where `detail` is the full reqwest
    // message and `cause` is the underlying transport error detail. Mirror
    // Node's shape: `TypeError: "fetch failed"` with the detail in `.cause`.
    const cause = resp.error[1];
    throw new TypeError("fetch failed", { cause: new Error(cause) });
  }
  if (terminator.aborted) {
    // op_fetch_send resolved successfully, so the FetchResponseResource is already in
    // the resource table. The success path below either closes resp.responseRid
    // (redirect / null-body / HEAD / CONNECT) or hands it to createResponseBodyStream,
    // which owns its lifecycle. Only this aborted-after-resolve branch needs to close
    // the rid manually, otherwise it leaks and trips the test sanitizer.
    core.tryClose(resp.responseRid);
    return abortedNetworkError();
  }

  processUrlList(req.urlList, req.urlListProcessed);

  /** @type {InnerResponse} */
  const response = {
    headerList: resp.headers,
    status: resp.status,
    body: null,

View on GitHub (pinned to a961cdec3b)