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
- Read err.cause.message first — it contains the actual transport reason (dns error, tcp connect error, invalid certificate, ...)
- Connection refused: verify host/port and that the server listens on the right interface
- TLS problems: fix the certificate chain or pass the CA with deno run --cert=ca.pem
- Proxy problems: correct or unset HTTP_PROXY/HTTPS_PROXY/NO_PROXY for the target host
- DNS problems: test with Deno.resolveDns or curl from the same environment
- 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
- Always log err.cause, not just 'fetch failed'
- Health-check dependencies at startup instead of failing on first request
- Retry only idempotent methods, with capped backoff and jitter
- Verify proxy env vars and CA configuration in containers and CI
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
- TLS wrap attach (JS stream) failed
- ${bindingName} ${errCodeMessage} ${name}
- Deno.autoUpdate: check failed:
- Dispatcher connect.ca must be a string, Buffer, or…
- Either `node` or `range` must be provided when reporting an…
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)