---
title: "TypeError: fetch failed no Node.js: causas e soluções"
description: "TypeError: fetch failed no Node.js esconde o erro real em err.cause. Veja como lê-lo e corrigir causas de DNS, conexão, timeout, certificado e proxy."
url: https://proxynet.io/pt-br/blog/typeerror-fetch-failed
date: 2026-10-06
author: "Acar Diveroli"
category: "Tutoriais, Web scraping"
lang: pt-BR
---

# TypeError: fetch failed no Node.js: causas e soluções

Seu script em Node.js chama a mesma API a cada dez minutos há semanas. Você o leva para um runner de CI, ou um colega o inicia na rede do escritório, e de repente toda chamada termina com uma única linha: `TypeError: fetch failed`. Sem nome de host, sem código de status. A URL abre no navegador e o `curl` a alcança da mesma máquina, então a mensagem parece não dizer nada.

Ela diz algo, só que não na mensagem. Este guia mostra onde o Node.js guarda o motivo real, as causas que reproduzimos no Node.js 22, 24 e 26, por que o fetch ignora as variáveis de proxy, a incompatibilidade do undici que quebra agents de proxy no Node.js 26 e um script testado que dá nome à causa.

> **Nota: Resposta rápida**
>
> `TypeError: fetch failed` é o que o fetch nativo do Node.js lança em qualquer falha que acontece antes de chegar uma resposta HTTP; o motivo está em `err.cause`, então registre isso em vez de `err.message`. `ENOTFOUND` significa que o nome do host não foi resolvido, `ECONNREFUSED` que nada escuta na porta, `ECONNRESET` ou `UND_ERR_SOCKET` que a conexão foi cortada, e `UND_ERR_CONNECT_TIMEOUT` que nenhuma conexão foi aberta em 10 segundos. Atrás de um proxy, o fetch ignora `HTTPS_PROXY`, a menos que o Node.js rode com `NODE_USE_ENV_PROXY=1`. `invalid onError method` significa que um agent do pacote undici do npm não combina com o undici que vem dentro do Node.js.

## O que significa "TypeError: fetch failed"?

O `fetch()` global do Node.js, disponível desde a versão 18, é construído sobre o undici, um cliente HTTP que vem dentro de toda versão do Node.js. O [padrão Fetch](https://fetch.spec.whatwg.org/#fetch-method) determina que uma requisição que termina em erro de rede seja rejeitada com um `TypeError`. O undici usa uma única mensagem fixa para todos esses erros, `fetch failed`, e anexa o erro real como `cause`.

Erro de rede é qualquer coisa que dá errado antes de uma resposta: a resolução do nome, a conexão TCP, o handshake TLS, o túnel do proxy ou uma conexão que fecha antes de os cabeçalhos chegarem. Um 404 ou um 500 não é erro de rede; o fetch é resolvido normalmente e `res.ok` é `false`.

Quando nada captura o erro, o próprio Node.js imprime a causa. Esta é a saída real de um script de uma linha com um host digitado errado no 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'
  }
}
```

O problema começa quando o código captura o erro e registra só a mensagem, como fazem muitos frameworks e SDKs. Imprimir a causa custa uma linha a mais:

```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
}
```

## Como encontrar o erro real por trás do fetch failed?

1. **Registre `err.cause`, não `err.message`.** Se um framework engolir a causa, envolva a chamada você mesmo.
2. **Leia `cause.code`.** Códigos que começam com `E` (`ENOTFOUND`, `ECONNRESET`) vêm do sistema operacional, códigos que começam com `UND_ERR_` vêm do undici, e códigos como `UNABLE_TO_GET_ISSUER_CERT_LOCALLY` vêm da camada TLS.
3. **Verifique se há um `AggregateError`.** Para `localhost`, o Node.js tenta vários endereços. No nosso teste, `cause.message` veio vazio e `cause.errors` trazia um `ECONNREFUSED` para `::1` e outro para `127.0.0.1`.
4. **Desça mais um nível nos erros de proxy.** Um login de proxy rejeitado deu `Request was cancelled.` sem código; o status estava em `err.cause.cause`: `Proxy response (407) !== 200 when HTTP Tunneling`.
5. **Observe o tempo.** Milissegundos indicam uma recusa, uma redefinição ou uma resposta de DNS; cerca de 10 segundos é o tempo limite de conexão; cinco minutos, a espera padrão pelos cabeçalhos.

## Códigos de err.cause: o que cada um significa e o que corrigir

Produzimos cada linha no Windows 11 com o Node.js 24.21.0 e 26.10.0, usando um proxy de teste local e servidores locais que recusam, redefinem ou travam conexões, ou apresentam certificados de teste; as duas versões deram os mesmos códigos. A [referência de erros do undici](https://github.com/nodejs/undici/blob/main/docs/docs/api/Errors.md) documenta os códigos `UND_ERR_` e recomenda comparar `error.code` em vez de usar `instanceof`, já que o dispatcher pode vir de outra cópia do undici.

| `cause.code` e mensagem | O que aconteceu | O que verificar primeiro |
|---|---|---|
| `ENOTFOUND` getaddrinfo ENOTFOUND host | O nome do host não resolve | Grafia, DNS, VPN |
| `ECONNREFUSED` connect ECONNREFUSED 127.0.0.1:8999 | Nada escuta nessa porta | Serviço, porta, porta do proxy |
| `ECONNREFUSED` dentro de um `AggregateError` | O mesmo, para cada endereço de `localhost` | Inicie o servidor |
| `UND_ERR_CONNECT_TIMEOUT` Connect Timeout Error | Nenhuma conexão TCP em 10 s | Endereço, firewall |
| `ECONNRESET` read ECONNRESET | Cortada com uma redefinição TCP | Proxy, firewall; nova tentativa |
| `UND_ERR_SOCKET` other side closed | Fechada antes de qualquer resposta | Logs do servidor; nova tentativa |
| `UND_ERR_HEADERS_TIMEOUT` Headers Timeout Error | Conectou, mas sem cabeçalhos a tempo | Seu limite de tempo |
| `UNABLE_TO_GET_ISSUER_CERT_LOCALLY`, `SELF_SIGNED_CERT_IN_CHAIN` | Certificado raiz não confiável | Certificado raiz da empresa |
| `UNABLE_TO_VERIFY_LEAF_SIGNATURE` | O servidor não enviou o intermediário | Cadeia de certificados do servidor |
| `ERR_SSL_WRONG_VERSION_NUMBER` | TLS enviado a uma porta HTTP sem criptografia | `https://` na URL do proxy |
| `UND_ERR_INVALID_ARG` invalid onError method | Agent de outra versão principal do undici | O `fetch` do próprio undici |
| Sem código: Request was cancelled. | O proxy recusou o túnel | Credenciais do proxy |

Há uma mensagem relacionada que não é `fetch failed`: se os cabeçalhos chegam mas o corpo trava, `res.text()` ou `res.json()` lança `TypeError: terminated`, no nosso teste com `UND_ERR_BODY_TIMEOUT` como causa.

## ENOTFOUND e ECONNREFUSED: a requisição nunca chegou a um servidor

`ENOTFOUND` vem da resolução de nomes: o resolvedor disse que o nome não existe. Procure um erro de digitação, uma VPN com servidores DNS próprios ou um contêiner que não alcança o DNS interno da sua empresa. Atrás de um proxy, quem resolve o nome é o proxy, então um host errado volta como código de status do proxy: nosso proxy de teste respondeu 502, informado como `Proxy response (502) !== 200 when HTTP Tunneling`.

`ECONNREFUSED` significa que a máquina respondeu, mas nada aceita conexões naquela porta. Se o endereço na mensagem é o do seu proxy, o que está errado é a configuração do proxy, não o destino. Com `localhost`, o Node.js tenta tanto `::1` quanto `127.0.0.1`; no nosso teste, um servidor vinculado só a `127.0.0.1` ainda respondeu a `http://localhost`. Quando nenhum dos dois responde, o servidor de desenvolvimento está fora do ar ou usa outra porta, ou o seu código roda no Docker, onde `localhost` é o próprio contêiner.

## ECONNRESET, "other side closed" e "socket hang up"

Os três significam que uma conexão foi aberta e depois se rompeu. Comparamos o fetch com o módulo `http` e com o Axios 1.20:

- Um servidor que respondeu com uma redefinição TCP produziu `read ECONNRESET` nos três.
- Um servidor que fechou a conexão sem responder produziu `UND_ERR_SOCKET` (`other side closed`) no fetch, e `socket hang up` com o código `ECONNRESET` no `http` e no Axios.

Motivos típicos: o servidor caiu no meio da requisição, um balanceador de carga ou proxy fechou uma conexão keep-alive ociosa bem quando o seu cliente a reutilizava, ou um firewall derrubou uma conexão longa. Alguns servidores e filtros de bots também fecham conexões que não querem atender; isso significa ir mais devagar ou pedir acesso, não insistir com mais tentativas.

Repita requisições idempotentes (GET, HEAD) uma ou duas vezes com uma espera crescente. Não repita às cegas um POST que cria um pedido: o servidor pode ter feito o trabalho antes de a conexão cair.

## Tempos limite: UND_ERR_CONNECT_TIMEOUT, UND_ERR_HEADERS_TIMEOUT e AbortSignal.timeout()

O fetch no Node.js não tem um limite de tempo total. O undici desiste de abrir uma conexão TCP depois de 10 segundos, depois espera até 300 segundos pelos cabeçalhos e até 300 segundos entre partes do corpo. Um servidor que aceita a conexão e trava pode segurar uma requisição por cinco minutos. Defina o seu próprio limite:

```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);
}
```

Contra um servidor local que nunca responde, o código imprimiu `gave up after 5 s` no Node.js 24 e 26. Como explica a [página da MDN sobre AbortSignal.timeout()](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal/timeout_static), o sinal aborta com um `TimeoutError`, não com `fetch failed`, então verifique `err.name`. O limite também cobre o corpo: um corpo travado fez `res.text()` lançar o mesmo `TimeoutError`. Mudar os limites do próprio undici exige um `Agent` do pacote do npm, o que traz a questão de versões que vemos mais abaixo.

## Por que o fetch ignora HTTP_PROXY e HTTPS_PROXY?

O `curl`, o pip e muitas outras ferramentas leem as variáveis de ambiente de proxy; o fetch do Node.js não. Com `HTTPS_PROXY` definida, o fetch no Node.js 22.23.3, 24.21.0 e 26.10.0 foi direto ao destino e o log do nosso proxy ficou vazio, enquanto o `curl` usou o proxy. Onde só o proxy chega à internet, a causa então aponta o destino (`ENOTFOUND`, `UND_ERR_CONNECT_TIMEOUT`), nunca o proxy que foi ignorado.

O [suporte nativo a proxy](https://nodejs.org/api/http.html#built-in-proxy-support) do Node.js liga esse comportamento: `NODE_USE_ENV_PROXY=1` (Node.js 22.21.0 e 24.0.0 ou posteriores) ou a flag `--use-env-proxy` (22.21.0 e 24.5.0 ou posteriores). O Node.js então lê `HTTP_PROXY`, `HTTPS_PROXY` e `NO_PROXY` na inicialização, para o fetch e para os módulos `http` e `https`. A documentação ainda marca o recurso como em desenvolvimento ativo.

```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
```

No PowerShell:

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

Nosso proxy de teste então registrou `CONNECT example.com:443` nas três versões; o Node.js 22 também avisou que o `EnvHttpProxyAgent` é experimental. Três detalhes costumam confundir:

- **A URL do proxy começa com `http://`, mesmo para destinos `https://`.** A requisição do túnel é HTTP sem criptografia e o TLS roda dentro dela; `https://` na frente de um proxy HTTP sem criptografia deu `ERR_SSL_WRONG_VERSION_NUMBER`.
- **`http.setGlobalProxyFromEnv()` faz o mesmo pelo código**, a partir do Node.js 24.14.0 e 25.4.0; o Node.js 22 não tem essa função.
- **Uma senha errada se esconde um nível mais abaixo**, em `err.cause.cause`.

O gateway de um provedor, como o dos nossos [Proxies residenciais](https://proxynet.io/pt-br/residential-proxy), entra na mesma variável como `http://user:pass@pr.proxynet.io:8000`, ou sem credenciais quando o IP do seu servidor já estiver na whitelist de IP.

Para proxy por requisição, Axios e SOCKS5, siga o nosso guia de [como usar proxy no Node.js](/pt-br/blog/nodejs-proxy).

## Node.js 26 e "invalid onError method": a incompatibilidade de versões do undici

Cada versão do Node.js traz o próprio undici, separado do pacote `undici` do npm: o Node.js 22.23.3 inclui o undici 6.28.1, o 24.21.0 inclui o 7.29.1 e o 26.10.0 inclui o 8.10.2. O undici 8.0.0 removeu os wrappers que permitiam que código de handler mais antigo conversasse com código mais novo. Segundo o [calendário de versões do Node.js](https://github.com/nodejs/Release), o Node.js 26 passa a ser a linha Active LTS em 28 de outubro de 2026, então muitos projetos vão topar com isso na atualização.

O erro aparece quando um `ProxyAgent` ou `Agent` do pacote do npm é passado como `dispatcher` ao fetch **global**. Testamos quatro versões principais do npm em três versões do Node.js:

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

Cada falha foi `TypeError: fetch failed` com `UND_ERR_INVALID_ARG` como causa. Uma variante mais silenciosa é pior: no Node.js 26, `setGlobalDispatcher(new ProxyAgent(...))` do undici 5 ou 6 não gerou erro nenhum, o fetch o ignorou e a requisição saiu direto, enquanto nosso proxy não via nada.

A correção é pegar o `fetch` do mesmo pacote que o agent:

```js
import { fetch, ProxyAgent } from "undici"; // fetch e o agent do mesmo pacote

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 no nosso teste, por um proxy de teste local
```

Isso funcionou com o undici 8.11.2 no Node.js 24 e 26. Se a incompatibilidade estiver em uma ferramenta que você não escreveu, como uma CLI que embute um undici antigo e cria um agent a partir das suas variáveis de proxy, atualize a ferramenta ou mantenha-a no Node.js 24 até sair a correção.

## Erros de certificado por trás do fetch failed

Uma verificação TLS que falha também chega como `fetch failed`, com o código do certificado em `cause.code`. No trabalho, a origem comum é um proxy corporativo ou antivírus que inspeciona HTTPS e assina o tráfego de novo com o próprio certificado raiz. O Node.js verifica contra a própria lista embutida de autoridades raiz, não contra a do sistema operacional, então rejeita essa raiz com `UNABLE_TO_GET_ISSUER_CERT_LOCALLY` ou `SELF_SIGNED_CERT_IN_CHAIN`.

Aponte `NODE_EXTRA_CA_CERTS` para um arquivo PEM com o certificado raiz da empresa, ou rode o Node.js com `--use-system-ca` (a partir do 22.15.0 e do 23.8.0) se a raiz estiver instalada na máquina. No nosso teste, `NODE_EXTRA_CA_CERTS` resolveu o primeiro caso, mas não `UNABLE_TO_VERIFY_LEAF_SIGNATURE`, em que o servidor deixa de enviar o certificado intermediário e só o dono dele pode corrigir. As correções para npm, Git, Python e curl estão em [Unable to get local issuer certificate](/pt-br/blog/unable-to-get-local-issuer-certificate).

## Um wrapper de fetch que dá nome à causa e tenta de novo

O script usa o fetch global, então não precisa de pacotes. Ele percorre toda a cadeia de causas, limita cada tentativa com `AbortSignal.timeout()` e só tenta de novo em redefinições, sockets fechados e tempos limite esgotados, com backoff exponencial mais jitter. Falhas de DNS, recusas, erros de certificado e um login de proxy rejeitado falham do mesmo jeito toda vez, então ele para neles. Em `429` e `503`, ele espera o tempo indicado em `Retry-After`.

Um `429` é o servidor pedindo que você vá mais devagar; o que o cabeçalho significa e como controlar o ritmo de um trabalho está em [HTTP 429 Too Many Requests](/pt-br/blog/http-429-too-many-requests).

```js
// fetch-check.mjs: dá nome à causa real por trás de "TypeError: fetch failed" e só tenta de novo o que pode se recuperar.
// Testado no Node.js 22, 24 e 26. Proxy opcional: 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));

/** O erro e cada erro embrulhado dentro dele, do mais externo para o mais interno. */
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": um erro por endereço
  }
  return chain;
}

/** O primeiro código de erro em string na cadeia, como "ECONNRESET". */
const codeOf = (err) => causes(err).map((e) => e.code).find((c) => typeof c === "string") ?? "";

/** Uma linha: o que falhou e o que verificar primeiro. */
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 com limite de tempo rígido, backoff para falhas de rede e Retry-After para 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 ... mais 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; // só o formato em segundos
      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)}`);
  }
}
```

### Como fica a saída

Rodamos o script no Node.js 26.10.0 contra um host digitado errado, uma porta fechada em `localhost` e servidores locais que redefinem toda conexão (porta 8902), nunca respondem (8901), usam um certificado da nossa própria autoridade de teste (8906) e respondem `429` duas vezes antes de a requisição dar certo (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
```

Depois, pelo proxy de teste local com `NODE_USE_ENV_PROXY=1`: a senha certa, uma errada e `https://` na frente do endereço do proxy:

```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://
```

O Node.js 22.23.3 e o 24.21.0 imprimiram as mesmas linhas, tirando os valores aleatórios de backoff.

## Onde você encontra esse erro

- **Next.js e outros frameworks do lado do servidor**, cujas páginas de erro muitas vezes mostram só a mensagem.
- **Servidores MCP e ferramentas de agentes de IA**, que muitas vezes rodam atrás de uma VPN ou de um proxy corporativo.
- **SDKs construídos sobre o fetch**, alguns dos quais descartavam a causa em versões antigas.
- **Runners de CI e builds do Docker** que não têm as variáveis de proxy nem o certificado raiz da empresa que o host tem.
- **Trabalhos de scraping e de monitoramento**, que em execuções longas esbarram em quase toda a tabela.

Trabalhos longos de coleta de dados são os que mais ganham com o wrapper. Se um trabalho assim lida com poucos destinos conhecidos e precisa de IPs de saída que fiquem iguais por semanas, os [Proxies de datacenter](https://proxynet.io/pt-br/datacenter-proxy) dão a você endereços IPv4 dedicados com tráfego sem cota.

## Erros comuns

- **Registrar `err.message`.** Ela sempre diz `fetch failed`.
- **Esperar que o fetch leia `HTTPS_PROXY`.** Ele não lê sem `NODE_USE_ENV_PROXY=1` ou `--use-env-proxy`.
- **Passar um agent do undici do npm para o fetch global.** Importe o `fetch` do mesmo pacote.
- **Desligar a verificação de certificados.** `NODE_TLS_REJECT_UNAUTHORIZED=0` desativa a verificação para o processo inteiro, então um atacante no caminho fica igual ao seu proxy corporativo.
- **Tentar de novo em tudo.** Um erro de digitação falha do mesmo jeito toda vez; um POST depois de `ECONNRESET` pode rodar duas vezes.
- **Não definir limite de tempo.** Um servidor travado pode segurar uma requisição por cinco minutos.

## Guia de decisão

| O que você vê em `err.cause` | O que fazer |
|---|---|
| `ENOTFOUND` | Corrija o nome do host ou o DNS; sem nova tentativa |
| `ECONNREFUSED` com o endereço do seu proxy | Copie de novo o host e a porta do proxy |
| `ECONNREFUSED` em `localhost` | Inicie o servidor; verifique a porta e o Docker |
| `ECONNRESET`, `UND_ERR_SOCKET` | Repita requisições GET com backoff |
| `UND_ERR_CONNECT_TIMEOUT` | Verifique o firewall; atrás de um proxy corporativo, defina `NODE_USE_ENV_PROXY=1` |
| Espera longa e depois `UND_ERR_HEADERS_TIMEOUT` | Adicione `AbortSignal.timeout()` |
| Um código de certificado | `NODE_EXTRA_CA_CERTS` ou `--use-system-ca` |
| `Proxy response (407)` um nível abaixo | Verifique user:pass ou a whitelist de IP |
| `invalid onError method` | Importe o `fetch` do pacote undici do agent |

## Perguntas frequentes

### Por que o fetch não lança erro em um 404 ou 500?

Porque o servidor respondeu. O fetch só rejeita em erros de rede; um erro HTTP é resolvido com `res.ok` igual a `false`, então verifique isso antes de ler o corpo.

### "fetch failed" é o mesmo que "Failed to fetch" no navegador?

São parecidos, mas não iguais. O Chrome rejeita com `TypeError: Failed to fetch` e não diz à página o porquê, então um bloqueio de CORS parece idêntico; abra a guia **Rede** no DevTools. O Node.js coloca o motivo em `cause`.

### Como definir um tempo limite para o fetch no Node.js?

Passe `signal: AbortSignal.timeout(ms)`. Quando ele dispara, o fetch rejeita com um `TimeoutError`, e o limite também interrompe um corpo travado.

### O fetch do Node.js usa HTTP_PROXY e HTTPS_PROXY?

Só com `NODE_USE_ENV_PROXY=1` (22.21.0, 24.0.0 e posteriores) ou `--use-env-proxy` (22.21.0, 24.5.0 e posteriores). Caso contrário, as variáveis são ignoradas em silêncio.

### Por que o curl funciona quando o fetch falha na mesma máquina?

Normalmente por um de dois motivos: o `curl` lê as variáveis de proxy e o fetch não, e muitas builds do `curl`, incluindo a do Windows, usam o repositório de certificados do sistema, enquanto o Node.js usa a própria lista, a menos que você passe `--use-system-ca`.

### Um proxy pode resolver "TypeError: fetch failed"?

Só quando o caminho de rede é a causa, como em uma rede corporativa em que só o proxy chega à internet. Ele não corrige um erro de digitação, um problema de certificado ou um servidor fora do ar, e não é um jeito de contornar um site que derruba sua conexão ou aplica limite de taxa: vá mais devagar, use a API oficial ou peça acesso.

## Em resumo

`TypeError: fetch failed` é só um invólucro: o motivo está em `err.cause`, às vezes um nível mais abaixo. Leia o código, corrija o que ele aponta, dê a cada chamada um `AbortSignal.timeout()` e só tente de novo em redefinições e tempos limite esgotados. Atrás de um proxy, defina `NODE_USE_ENV_PROXY=1`, mantenha `http://` na URL do proxy e pegue o `fetch` e o agent do mesmo pacote undici, principalmente no Node.js 26. Quando o seu código estiver pronto para um proxy, compare as opções na nossa página de [serviços de proxy](/pt-br/proxy).
