---
title: "TypeError: fetch failed in Node.js: Ursachen und Lösungen"
description: "TypeError: fetch failed in Node.js verbirgt die Ursache in err.cause. So lesen Sie sie und beheben DNS-, Verbindungs-, Timeout-, Zertifikats- und Proxy-Fehler."
url: https://proxynet.io/de/blog/typeerror-fetch-failed
date: 2026-10-06
author: "Acar Diveroli"
category: "Anleitungen, Web Scraping"
lang: de
---

# TypeError: fetch failed in Node.js: Ursachen und Lösungen

Ihr Node.js-Skript ruft seit Wochen alle zehn Minuten dieselbe API auf. Sie verschieben es auf einen CI-Runner, oder ein Kollege startet es im Büronetz, und plötzlich endet jeder Aufruf mit einer einzigen Zeile: `TypeError: fetch failed`. Kein Hostname, kein Statuscode. Die URL öffnet sich im Browser, und `curl` erreicht sie vom selben Rechner aus, also scheint die Meldung nichts zu sagen.

Sie sagt durchaus etwas, nur nicht in der Meldung selbst. Dieser Leitfaden zeigt, wo Node.js den eigentlichen Grund ablegt, welche Ursachen wir unter Node.js 22, 24 und 26 nachgestellt haben, warum fetch Proxy-Variablen ignoriert, welche undici-Inkompatibilität unter Node.js 26 Proxy-Agents lahmlegt, und ein getestetes Skript, das die Ursache benennt.

> **Hinweis: Kurzantwort**
>
> `TypeError: fetch failed` wirft das integrierte fetch von Node.js bei jedem Fehler, der auftritt, bevor eine HTTP-Antwort eintrifft; der Grund steht in `err.cause`, protokollieren Sie also diesen Wert statt `err.message`. `ENOTFOUND` bedeutet, dass der Hostname nicht aufgelöst wurde, `ECONNREFUSED`, dass auf dem Port niemand lauscht, `ECONNRESET` oder `UND_ERR_SOCKET`, dass die Verbindung abgebrochen wurde, und `UND_ERR_CONNECT_TIMEOUT`, dass innerhalb von 10 Sekunden keine Verbindung zustande kam. Hinter einem Proxy ignoriert fetch `HTTPS_PROXY`, solange Node.js nicht mit `NODE_USE_ENV_PROXY=1` läuft. `invalid onError method` bedeutet, dass ein Agent aus dem npm-Paket undici nicht zu dem undici in Node.js passt.

## Was bedeutet „TypeError: fetch failed“?

Das globale `fetch()` von Node.js, verfügbar seit Version 18, basiert auf undici, einem HTTP-Client, der in jeder Node.js-Version mitgeliefert wird. Der [Fetch-Standard](https://fetch.spec.whatwg.org/#fetch-method) legt fest, dass eine Anfrage, die mit einem Netzwerkfehler endet, mit einem `TypeError` zurückgewiesen wird. Undici verwendet für alle diese Fehler dieselbe feste Meldung, `fetch failed`, und hängt den eigentlichen Fehler als `cause` an.

Ein Netzwerkfehler ist alles, was vor einer Antwort schiefgeht: die Namensauflösung, die TCP-Verbindung, der TLS-Handshake, der Proxy-Tunnel oder eine Verbindung, die sich schließt, bevor die Header ankommen. Ein 404 oder 500 gehört nicht dazu; fetch wird normal erfüllt, und `res.ok` ist `false`.

Fängt niemand den Fehler ab, gibt Node.js die Ursache selbst aus. Das ist die echte Ausgabe eines einzeiligen Skripts mit falsch geschriebenem Host unter Node.js 26.10.0:

```text
[TypeError: fetch failed] {
  [cause]: Error: getaddrinfo ENOTFOUND api.example-typo.invalid
      at GetAddrInfoReqWrap.onlookupall [as oncomplete] (node:dns:122:26) {
    errno: -3008,
    code: 'ENOTFOUND',
    syscall: 'getaddrinfo',
    hostname: 'api.example-typo.invalid'
  }
}
```

Schwierig wird es, wenn Code den Fehler abfängt und nur die Meldung protokolliert, wie es viele Frameworks und SDKs tun. Die Ursache auszugeben kostet eine Zeile mehr:

```js
try {
  await fetch("https://api.example-typo.invalid/v1/items");
} catch (err) {
  console.log(err.message);                         // fetch failed
  console.log(err.cause?.code, err.cause?.message); // ENOTFOUND getaddrinfo ENOTFOUND api.example-typo.invalid
}
```

## Wie finden Sie den echten Fehler hinter fetch failed?

1. **Protokollieren Sie `err.cause`, nicht `err.message`.** Verschluckt ein Framework die Ursache, umschließen Sie den Aufruf selbst.
2. **Lesen Sie `cause.code`.** Codes, die mit `E` beginnen (`ENOTFOUND`, `ECONNRESET`), stammen vom Betriebssystem, Codes mit `UND_ERR_` von undici und Codes wie `UNABLE_TO_GET_ISSUER_CERT_LOCALLY` von der TLS-Schicht.
3. **Achten Sie auf einen `AggregateError`.** Für `localhost` probiert Node.js mehrere Adressen. In unserem Test war `cause.message` leer, und `cause.errors` enthielt je ein `ECONNREFUSED` für `::1` und für `127.0.0.1`.
4. **Gehen Sie bei Proxy-Fehlern eine Ebene tiefer.** Eine abgelehnte Proxy-Anmeldung ergab `Request was cancelled.` ohne Code; der Status stand in `err.cause.cause`: `Proxy response (407) !== 200 when HTTP Tunneling`.
5. **Achten Sie auf die Dauer.** Millisekunden deuten auf eine Ablehnung, einen Reset oder eine DNS-Antwort hin; rund 10 Sekunden sind das Verbindungs-Timeout, fünf Minuten die Standardwartezeit auf die Header.

## err.cause-Codes: was sie bedeuten und was zu beheben ist

Jede Zeile haben wir unter Windows 11 mit Node.js 24.21.0 und 26.10.0 erzeugt, mit einem lokalen Test-Proxy und lokalen Servern, die Verbindungen ablehnen, zurücksetzen, hängen lassen oder Testzertifikate vorlegen; beide Versionen lieferten dieselben Codes. Die [Fehlerreferenz von undici](https://github.com/nodejs/undici/blob/main/docs/docs/api/Errors.md) dokumentiert die `UND_ERR_`-Codes und rät, auf `error.code` zu prüfen statt mit `instanceof`, da der Dispatcher aus einer anderen undici-Kopie stammen kann.

| `cause.code` und Meldung | Was passiert ist | Zuerst prüfen |
|---|---|---|
| `ENOTFOUND` getaddrinfo ENOTFOUND host | Der Hostname lässt sich nicht auflösen | Schreibweise, DNS, VPN |
| `ECONNREFUSED` connect ECONNREFUSED 127.0.0.1:8999 | Auf diesem Port lauscht niemand | Dienst, Port, Proxy-Port |
| `ECONNREFUSED` in einem `AggregateError` | Dasselbe, für jede Adresse von `localhost` | Server starten |
| `UND_ERR_CONNECT_TIMEOUT` Connect Timeout Error | Keine TCP-Verbindung innerhalb von 10 s | Adresse, Firewall |
| `ECONNRESET` read ECONNRESET | Per TCP-Reset abgebrochen | Proxy, Firewall; wiederholen |
| `UND_ERR_SOCKET` other side closed | Vor jeder Antwort geschlossen | Server-Logs; wiederholen |
| `UND_ERR_HEADERS_TIMEOUT` Headers Timeout Error | Verbunden, aber keine Header rechtzeitig | Ihr Zeitlimit |
| `UNABLE_TO_GET_ISSUER_CERT_LOCALLY`, `SELF_SIGNED_CERT_IN_CHAIN` | Stammzertifikat nicht vertrauenswürdig | Stammzertifikat der Firma |
| `UNABLE_TO_VERIFY_LEAF_SIGNATURE` | Server hat kein Zwischenzertifikat gesendet | Zertifikatskette des Servers |
| `ERR_SSL_WRONG_VERSION_NUMBER` | TLS an einen reinen HTTP-Port gesendet | `https://` in der Proxy-URL |
| `UND_ERR_INVALID_ARG` invalid onError method | Agent aus einer anderen undici-Hauptversion | Das eigene `fetch` von undici |
| Kein Code: Request was cancelled. | Proxy hat den Tunnel abgelehnt | Proxy-Zugangsdaten |

Eine verwandte Meldung lautet nicht `fetch failed`: Kommen die Header an, aber der Body stockt, wirft `res.text()` oder `res.json()` den Fehler `TypeError: terminated`, in unserem Test mit `UND_ERR_BODY_TIMEOUT` als Ursache.

## ENOTFOUND und ECONNREFUSED: Die Anfrage hat keinen Server erreicht

`ENOTFOUND` kommt aus der Namensauflösung: Der Resolver meldet, dass es den Namen nicht gibt. Suchen Sie nach einem Tippfehler, einem VPN mit eigenen DNS-Servern oder einem Container, der das interne DNS Ihres Unternehmens nicht erreicht. Hinter einem Proxy löst der Proxy den Namen auf, daher kommt ein falscher Host stattdessen als Statuscode des Proxys zurück: Unser Test-Proxy antwortete mit 502, gemeldet als `Proxy response (502) !== 200 when HTTP Tunneling`.

`ECONNREFUSED` bedeutet, dass der Rechner antwortet, auf diesem Port aber nichts Verbindungen annimmt. Steht in der Meldung die Adresse Ihres Proxys, ist die Proxy-Einstellung falsch, nicht das Ziel. Bei `localhost` probiert Node.js sowohl `::1` als auch `127.0.0.1`; in unserem Test antwortete ein Server, der nur an `127.0.0.1` gebunden war, trotzdem auf `http://localhost`. Antwortet keine der beiden Adressen, ist der Entwicklungsserver aus oder nutzt einen anderen Port, oder Ihr Code läuft in Docker, wo `localhost` der Container selbst ist.

## ECONNRESET, „other side closed“ und „socket hang up“

Alle drei bedeuten, dass eine Verbindung aufgebaut wurde und dann abbrach. Wir haben fetch mit dem `http`-Modul und Axios 1.20 verglichen:

- Ein Server, der mit einem TCP-Reset antwortete, erzeugte in allen dreien `read ECONNRESET`.
- Ein Server, der die Verbindung ohne Antwort schloss, erzeugte in fetch `UND_ERR_SOCKET` (`other side closed`) und in `http` und Axios `socket hang up` mit dem Code `ECONNRESET`.

Typische Gründe: Der Server ist mitten in der Anfrage abgestürzt, ein Load Balancer oder Proxy hat eine inaktive Keep-alive-Verbindung genau in dem Moment geschlossen, als Ihr Client sie wiederverwenden wollte, oder eine Firewall hat eine lange Verbindung gekappt. Manche Server und Bot-Filter schließen auch Verbindungen, die sie nicht bedienen wollen; dann heißt es langsamer werden oder um Zugang bitten, nicht hartnäckiger wiederholen.

Wiederholen Sie idempotente Anfragen (GET, HEAD) ein- oder zweimal mit wachsender Wartezeit. Wiederholen Sie nicht blind einen POST, der eine Bestellung anlegt: Der Server hat die Arbeit womöglich schon erledigt, bevor die Verbindung abbrach.

## Timeouts: UND_ERR_CONNECT_TIMEOUT, UND_ERR_HEADERS_TIMEOUT und AbortSignal.timeout()

Fetch hat in Node.js kein Gesamtzeitlimit. Undici gibt einen TCP-Verbindungsaufbau nach 10 Sekunden auf, wartet dann bis zu 300 Sekunden auf die Header und bis zu 300 Sekunden zwischen zwei Teilen des Bodys. Ein Server, der die Verbindung annimmt und dann hängt, kann eine Anfrage fünf Minuten lang festhalten. Setzen Sie Ihr eigenes Limit:

```js
try {
  const res = await fetch("https://api.example.com/v1/items", { signal: AbortSignal.timeout(5_000) });
  console.log(res.status);
} catch (err) {
  if (err.name === "TimeoutError") console.log("gave up after 5 s");
  else console.log(err.message, err.cause?.code);
}
```

Gegen einen lokalen Server, der nie antwortet, gab der Code unter Node.js 24 und 26 `gave up after 5 s` aus. Wie [MDN zu AbortSignal.timeout()](https://developer.mozilla.org/de/docs/Web/API/AbortSignal/timeout_static) erklärt, bricht das Signal mit einem `TimeoutError` ab, nicht mit `fetch failed`; prüfen Sie also `err.name`. Das Limit gilt auch für den Body: Ein hängender Body ließ `res.text()` denselben `TimeoutError` werfen. Um die eigenen Limits von undici zu ändern, brauchen Sie einen `Agent` aus dem npm-Paket, und damit stellt sich die Versionsfrage weiter unten.

## Warum ignoriert fetch HTTP_PROXY und HTTPS_PROXY?

`curl`, pip und viele andere Tools lesen die Proxy-Umgebungsvariablen; das fetch von Node.js nicht. Mit gesetztem `HTTPS_PROXY` ging fetch unter Node.js 22.23.3, 24.21.0 und 26.10.0 direkt zum Ziel, und das Log unseres Proxys blieb leer, während `curl` den Proxy nutzte. Wo nur der Proxy ins Internet kommt, nennt die Ursache dann das Ziel (`ENOTFOUND`, `UND_ERR_CONNECT_TIMEOUT`), nie den übergangenen Proxy.

Die [integrierte Proxy-Unterstützung](https://nodejs.org/api/http.html#built-in-proxy-support) von Node.js schaltet das ein: `NODE_USE_ENV_PROXY=1` (ab Node.js 22.21.0 und 24.0.0) oder die Option `--use-env-proxy` (ab 22.21.0 und 24.5.0). Node.js liest dann beim Start `HTTP_PROXY`, `HTTPS_PROXY` und `NO_PROXY`, für fetch ebenso wie für die Module `http` und `https`. Die Dokumentation kennzeichnet die Funktion noch als in aktiver Entwicklung.

```bash
NODE_USE_ENV_PROXY=1 HTTPS_PROXY="http://user:pass@pr.proxynet.io:8000" NO_PROXY="localhost,127.0.0.1" node app.mjs
```

In PowerShell:

```powershell
$env:NODE_USE_ENV_PROXY = "1"
$env:HTTPS_PROXY = "http://user:pass@pr.proxynet.io:8000"
node app.mjs
```

Unser Test-Proxy protokollierte dann in allen drei Versionen `CONNECT example.com:443`; Node.js 22 warnte zusätzlich, dass `EnvHttpProxyAgent` experimentell ist. Drei Details sorgen oft für Verwirrung:

- **Die Proxy-URL beginnt mit `http://`, auch für `https://`-Ziele.** Die Tunnelanfrage ist reines HTTP, und TLS läuft darin; `https://` vor einem reinen HTTP-Proxy ergab `ERR_SSL_WRONG_VERSION_NUMBER`.
- **`http.setGlobalProxyFromEnv()` erledigt dasselbe aus dem Code heraus**, ab Node.js 24.14.0 und 25.4.0; Node.js 22 fehlt die Funktion.
- **Ein falsches Passwort versteckt sich eine Ebene tiefer**, in `err.cause.cause`.

Die Gateway-Adresse eines Anbieters, bei unserem [Residential-Proxy](https://proxynet.io/de/residential-proxy) etwa `http://user:pass@pr.proxynet.io:8000`, kommt in dieselbe Variable; steht die IP Ihres Servers auf der IP-Whitelist, auch ohne Zugangsdaten.

Für Proxys pro Anfrage, Axios und SOCKS5 folgen Sie unserem Leitfaden [Proxys in Node.js verwenden](/de/blog/nodejs-proxy).

## Node.js 26 und „invalid onError method“: die undici-Versionsinkompatibilität

Jede Node.js-Version bringt ihr eigenes undici mit, getrennt vom npm-Paket `undici`: Node.js 22.23.3 enthält undici 6.28.1, 24.21.0 enthält 7.29.1 und 26.10.0 enthält 8.10.2. Undici 8.0.0 hat die Wrapper entfernt, über die älterer Handler-Code mit neuerem Code sprechen konnte. Laut dem [Release-Zeitplan von Node.js](https://github.com/nodejs/Release) wird Node.js 26 am 28. Oktober 2026 zur Active-LTS-Linie, sodass viele Projekte beim Upgrade darauf stoßen werden.

Der Fehler tritt auf, wenn ein `ProxyAgent` oder `Agent` aus dem npm-Paket als `dispatcher` an das **globale** fetch übergeben wird. Wir haben vier npm-Hauptversionen unter drei Node.js-Versionen getestet:

| npm undici | Node.js 22.23.3 | Node.js 24.21.0 | Node.js 26.10.0 |
|---|---|---|---|
| 5.29.0 | funktioniert | funktioniert | invalid onError method |
| 6.29.0 | funktioniert | funktioniert | invalid onError method |
| 7.30.0 | funktioniert | funktioniert | funktioniert |
| 8.11.2 | invalid onRequestStart method | invalid onRequestStart method | funktioniert |

Jeder Fehlschlag war `TypeError: fetch failed` mit `UND_ERR_INVALID_ARG` als Ursache. Schlimmer ist eine stillere Variante: Unter Node.js 26 löste `setGlobalDispatcher(new ProxyAgent(...))` aus undici 5 oder 6 keinen Fehler aus, fetch ignorierte den Dispatcher, und die Anfrage ging direkt hinaus, während unser Proxy nichts sah.

Die Lösung: Nehmen Sie `fetch` aus demselben Paket wie den Agent.

```js
import { fetch, ProxyAgent } from "undici"; // fetch und der Agent aus demselben Paket

const proxy = new ProxyAgent("http://user:pass@pr.proxynet.io:8000");
const res = await fetch("https://example.com/", { dispatcher: proxy });
console.log(res.status); // 200 in unserem Test, über einen lokalen Test-Proxy
```

Das funktionierte mit undici 8.11.2 unter Node.js 24 und 26. Steckt die Inkompatibilität in einem Tool, das Sie nicht selbst geschrieben haben, etwa einem CLI, das ein altes undici mitbringt und aus Ihren Proxy-Variablen einen Agent baut, aktualisieren Sie das Tool oder lassen Sie es auf Node.js 24, bis der Fehler behoben ist.

## Zertifikatsfehler hinter fetch failed

Eine fehlgeschlagene TLS-Prüfung kommt ebenfalls als `fetch failed` an, mit dem Zertifikatscode in `cause.code`. Am Arbeitsplatz ist die übliche Quelle ein Firmen-Proxy oder Virenschutz, der HTTPS untersucht und den Datenverkehr mit einem eigenen Stammzertifikat neu signiert. Node.js prüft gegen seine eigene, mitgelieferte Liste von Stammzertifizierungsstellen, nicht gegen die des Betriebssystems, und lehnt dieses Stammzertifikat daher mit `UNABLE_TO_GET_ISSUER_CERT_LOCALLY` oder `SELF_SIGNED_CERT_IN_CHAIN` ab.

Lassen Sie `NODE_EXTRA_CA_CERTS` auf eine PEM-Datei mit dem Stammzertifikat der Firma zeigen, oder starten Sie Node.js mit `--use-system-ca` (ab 22.15.0 und 23.8.0), wenn das Stammzertifikat auf dem Rechner installiert ist. In unserem Test behob `NODE_EXTRA_CA_CERTS` den ersten Fall, aber nicht `UNABLE_TO_VERIFY_LEAF_SIGNATURE`: Dort lässt der Server sein Zwischenzertifikat weg, und nur sein Betreiber kann das beheben. Die Lösungen für npm, Git, Python und curl finden Sie in [Unable to get local issuer certificate](/de/blog/unable-to-get-local-issuer-certificate).

## Ein fetch-Wrapper, der die Ursache nennt und wiederholt

Das Skript nutzt das globale fetch und braucht daher keine Pakete. Es durchläuft die ganze Ursachenkette, begrenzt jeden Versuch mit `AbortSignal.timeout()` und wiederholt nur Resets, geschlossene Sockets und Timeouts, mit exponentiellem Backoff plus Jitter. DNS-Fehler, abgelehnte Verbindungen, Zertifikatsfehler und eine abgelehnte Proxy-Anmeldung scheitern jedes Mal gleich, deshalb bricht es bei ihnen ab. Bei `429` und `503` wartet es so lange, wie `Retry-After` angibt.

Ein `429` ist die Bitte des Servers, langsamer zu werden; was der Header bedeutet und wie Sie das Tempo eines Jobs steuern, erklärt unser Artikel zu [HTTP 429 Too Many Requests](/de/blog/http-429-too-many-requests).

```js
// fetch-check.mjs: nennt die echte Ursache hinter "TypeError: fetch failed" und wiederholt nur, was sich erholen kann.
// Getestet unter Node.js 22, 24 und 26. Optionaler Proxy: NODE_USE_ENV_PROXY=1 HTTPS_PROXY=http://user:pass@pr.proxynet.io:8000

const RETRY_CODES = new Set(["ECONNRESET", "UND_ERR_SOCKET", "UND_ERR_CONNECT_TIMEOUT"]);
const CERT_CODES = /CERT|SELF_SIGNED|UNABLE_TO_(GET|VERIFY)/;
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

/** Der Fehler und jeder darin verpackte Fehler, der äußerste zuerst. */
function causes(err) {
  const chain = [];
  for (let e = err; e && chain.length < 8; e = e.cause) {
    chain.push(e);
    if (e instanceof AggregateError) chain.push(...e.errors); // "localhost": ein Fehler pro Adresse
  }
  return chain;
}

/** Der erste Fehlercode als String in der Kette, etwa "ECONNRESET". */
const codeOf = (err) => causes(err).map((e) => e.code).find((c) => typeof c === "string") ?? "";

/** Eine Zeile: was fehlgeschlagen ist und was zuerst zu prüfen ist. */
function explain(err) {
  if (err.name === "TimeoutError") return "no answer before our AbortSignal.timeout(): slow server or proxy";
  const chain = causes(err);
  const text = chain.map((e) => e.message).join(" | ");
  const code = codeOf(err);
  const tunnel = text.match(/Proxy response \((\d{3})\)/);
  if (tunnel?.[1] === "407") return "proxy wants credentials (407): check user:pass or the IP whitelist";
  if (tunnel) return `proxy refused the tunnel (${tunnel[1]}): the proxy could not or would not reach the target`;
  if (code === "ENOTFOUND") return `name ${chain.find((e) => e.hostname)?.hostname} does not resolve: typo, DNS or VPN`;
  if (code === "ECONNREFUSED") return "nothing listens on that address and port: wrong port, service down or wrong proxy port";
  if (code === "ECONNRESET") return "the connection was cut (TCP reset): server, proxy or firewall dropped it";
  if (code === "UND_ERR_SOCKET") return "the other side closed the connection before answering";
  if (code === "UND_ERR_CONNECT_TIMEOUT") return "no TCP connection within 10 s: wrong IP, firewall or blocked outbound port";
  if (code === "UND_ERR_HEADERS_TIMEOUT") return "connected, but no response headers in time";
  if (code === "ERR_SSL_WRONG_VERSION_NUMBER") return "TLS spoken to a plain-HTTP port: the proxy URL should start with http://";
  if (CERT_CODES.test(code)) return `certificate not trusted (${code}): add your CA with NODE_EXTRA_CA_CERTS`;
  return `unrecognised, read the innermost error: ${chain.at(-1).message}`;
}

/** GET mit festem Zeitlimit, Backoff bei Netzwerkaussetzern und Retry-After bei 429/503. */
async function getWithRetry(url, { attempts = 3, timeoutMs = 15_000 } = {}) {
  for (let attempt = 1; ; attempt++) {
    const backoff = 500 * 2 ** (attempt - 1) + Math.random() * 250; // 0,5 s, 1 s, 2 s ... plus Jitter
    let res;
    try {
      res = await fetch(url, { signal: AbortSignal.timeout(timeoutMs) });
    } catch (err) {
      const code = codeOf(err);
      const retryable = RETRY_CODES.has(code) || err.name === "TimeoutError";
      if (!retryable || attempt === attempts) throw err;
      console.log(`  attempt ${attempt}: ${code || err.name}, retrying in ${Math.round(backoff)} ms`);
      await sleep(backoff);
      continue;
    }
    if ((res.status === 429 || res.status === 503) && attempt < attempts) {
      const wait = Number(res.headers.get("retry-after")) * 1000 || backoff; // nur die Angabe in Sekunden
      await res.body?.cancel();
      console.log(`  attempt ${attempt}: HTTP ${res.status}, waiting ${Math.round(wait)} ms`);
      await sleep(wait);
      continue;
    }
    return res;
  }
}

for (const url of process.argv.slice(2)) {
  console.log(url);
  try {
    const res = await getWithRetry(url, { timeoutMs: 5_000 });
    console.log(`  OK: HTTP ${res.status}`);
  } catch (err) {
    console.log(`  FAILED: ${err.name}: ${err.message} -> ${explain(err)}`);
  }
}
```

### So sieht die Ausgabe aus

Wir haben das Skript unter Node.js 26.10.0 gegen einen falsch geschriebenen Host laufen lassen, gegen einen geschlossenen Port auf `localhost` und gegen lokale Server, die jede Verbindung zurücksetzen (Port 8902), nie antworten (8901), ein Zertifikat unserer eigenen Test-Zertifizierungsstelle nutzen (8906) und zweimal mit `429` antworten, bevor die Anfrage gelingt (8910):

```text
https://api.example-typo.invalid/
  FAILED: TypeError: fetch failed -> name api.example-typo.invalid does not resolve: typo, DNS or VPN
http://localhost:8999/
  FAILED: TypeError: fetch failed -> nothing listens on that address and port: wrong port, service down or wrong proxy port
http://127.0.0.1:8902/
  attempt 1: ECONNRESET, retrying in 652 ms
  attempt 2: ECONNRESET, retrying in 1115 ms
  FAILED: TypeError: fetch failed -> the connection was cut (TCP reset): server, proxy or firewall dropped it
http://127.0.0.1:8901/
  attempt 1: TimeoutError, retrying in 658 ms
  attempt 2: TimeoutError, retrying in 1024 ms
  FAILED: TimeoutError: The operation was aborted due to timeout -> no answer before our AbortSignal.timeout(): slow server or proxy
https://127.0.0.1:8906/
  FAILED: TypeError: fetch failed -> certificate not trusted (UNABLE_TO_GET_ISSUER_CERT_LOCALLY): add your CA with NODE_EXTRA_CA_CERTS
http://127.0.0.1:8910/items
  attempt 1: HTTP 429, waiting 1000 ms
  attempt 2: HTTP 429, waiting 1000 ms
  OK: HTTP 200
```

Dann über den lokalen Test-Proxy mit `NODE_USE_ENV_PROXY=1`: das richtige Passwort, ein falsches und `https://` vor der Proxy-Adresse:

```text
https://example.com/
  OK: HTTP 200
https://example.com/
  FAILED: TypeError: fetch failed -> proxy wants credentials (407): check user:pass or the IP whitelist
https://example.com/
  FAILED: TypeError: fetch failed -> TLS spoken to a plain-HTTP port: the proxy URL should start with http://
```

Node.js 22.23.3 und 24.21.0 gaben dieselben Zeilen aus, abgesehen von den zufälligen Backoff-Werten.

## Wo Sie auf diesen Fehler stoßen

- **Next.js und andere serverseitige Frameworks**, deren Fehlerseiten oft nur die Meldung zeigen.
- **MCP-Server und Tools für KI-Agenten**, die oft hinter einem VPN oder Firmen-Proxy laufen.
- **SDKs auf Basis von fetch**, von denen einige in älteren Versionen die Ursache verworfen haben.
- **CI-Runner und Docker-Builds**, denen die Proxy-Variablen und das Stammzertifikat der Firma vom Host fehlen.
- **Scraping- und Monitoring-Jobs**, die bei langen Läufen den Großteil der Tabelle zu sehen bekommen.

Lange Erfassungsjobs profitieren am meisten von dem Wrapper. Arbeitet ein solcher Job mit wenigen bekannten Zielen und braucht Ausgangs-IPs, die über Wochen gleich bleiben, liefert Ihnen ein [Datacenter-Proxy](https://proxynet.io/de/datacenter-proxy) dedizierte IPv4-Adressen mit Traffic ohne Kontingent.

## Häufige Fehler

- **`err.message` protokollieren.** Dort steht immer `fetch failed`.
- **Erwarten, dass fetch `HTTPS_PROXY` liest.** Nicht ohne `NODE_USE_ENV_PROXY=1` oder `--use-env-proxy`.
- **Einen Agent aus npm-undici an das globale fetch übergeben.** Importieren Sie `fetch` aus demselben Paket.
- **Zertifikatsprüfungen abschalten.** `NODE_TLS_REJECT_UNAUTHORIZED=0` deaktiviert die Prüfung für den ganzen Prozess, sodass ein Angreifer auf dem Weg genauso aussieht wie Ihr Firmen-Proxy.
- **Alles wiederholen.** Ein Tippfehler scheitert jedes Mal gleich; ein POST nach `ECONNRESET` läuft womöglich zweimal.
- **Kein Zeitlimit.** Ein hängender Server kann eine Anfrage fünf Minuten lang festhalten.

## Entscheidungshilfe

| Was Sie in `err.cause` sehen | Was zu tun ist |
|---|---|
| `ENOTFOUND` | Hostnamen oder DNS korrigieren; nicht wiederholen |
| `ECONNREFUSED` mit der Adresse Ihres Proxys | Proxy-Host und -Port neu kopieren |
| `ECONNREFUSED` bei `localhost` | Server starten; Port und Docker prüfen |
| `ECONNRESET`, `UND_ERR_SOCKET` | GET-Anfragen mit Backoff wiederholen |
| `UND_ERR_CONNECT_TIMEOUT` | Firewall prüfen; hinter einem Firmen-Proxy `NODE_USE_ENV_PROXY=1` setzen |
| Lange Wartezeit, dann `UND_ERR_HEADERS_TIMEOUT` | `AbortSignal.timeout()` ergänzen |
| Ein Zertifikatscode | `NODE_EXTRA_CA_CERTS` oder `--use-system-ca` |
| `Proxy response (407)` eine Ebene tiefer | user:pass oder die IP-Whitelist prüfen |
| `invalid onError method` | `fetch` aus dem undici-Paket des Agents importieren |

## Häufige Fragen

### Warum wirft fetch bei einem 404 oder 500 keinen Fehler?

Weil der Server geantwortet hat. Fetch weist nur bei Netzwerkfehlern zurück; ein HTTP-Fehler wird mit `res.ok` gleich `false` erfüllt, prüfen Sie den Wert also, bevor Sie den Body lesen.

### Ist „fetch failed“ dasselbe wie „Failed to fetch“ im Browser?

Verwandt, aber nicht dasselbe. Chrome weist mit `TypeError: Failed to fetch` zurück und sagt der Seite nicht, warum, sodass eine CORS-Sperre genauso aussieht; öffnen Sie in den DevTools den Tab **Netzwerk**. Node.js legt den Grund in `cause` ab.

### Wie setzen Sie ein Timeout für fetch in Node.js?

Übergeben Sie `signal: AbortSignal.timeout(ms)`. Wenn das Signal auslöst, weist fetch mit einem `TimeoutError` zurück, und das Limit stoppt auch einen hängenden Body.

### Nutzt fetch in Node.js HTTP_PROXY und HTTPS_PROXY?

Nur mit `NODE_USE_ENV_PROXY=1` (ab 22.21.0 und 24.0.0) oder `--use-env-proxy` (ab 22.21.0 und 24.5.0). Sonst werden die Variablen stillschweigend ignoriert.

### Warum funktioniert curl, wenn fetch auf demselben Rechner scheitert?

Meist aus einem von zwei Gründen: `curl` liest die Proxy-Variablen und fetch nicht, und viele `curl`-Builds, auch der in Windows, nutzen den Zertifikatsspeicher des Systems, während Node.js seine eigene Liste verwendet, solange Sie nicht `--use-system-ca` übergeben.

### Kann ein Proxy „TypeError: fetch failed“ beheben?

Nur wenn der Netzwerkweg die Ursache ist, etwa in einem Firmennetz, in dem nur der Proxy ins Internet kommt. Er behebt keinen Tippfehler, kein Zertifikatsproblem und keinen ausgefallenen Server, und er ist kein Weg, eine Website zu umgehen, die Ihre Verbindungen zurücksetzt oder per Rate Limit bremst: Werden Sie langsamer, nutzen Sie die offizielle API oder bitten Sie um Zugang.

## Fazit

`TypeError: fetch failed` ist nur eine Hülle: Der Grund steht in `err.cause`, manchmal eine Ebene tiefer. Lesen Sie den Code, beheben Sie, was er nennt, geben Sie jedem Aufruf ein `AbortSignal.timeout()` und wiederholen Sie nur Resets und Timeouts. Setzen Sie hinter einem Proxy `NODE_USE_ENV_PROXY=1`, behalten Sie `http://` in der Proxy-URL und nehmen Sie `fetch` und seinen Agent aus demselben undici-Paket, besonders unter Node.js 26. Wenn Ihr Code bereit für einen Proxy ist, vergleichen Sie die Optionen auf unserer Seite zu [Proxy-Diensten](/de/proxy).
