Como usar proxy no Node.js: Axios e node-fetch

Publicado:

13 min de leitura

Acar Diveroli
Autor: Acar Diveroli
Um cubo do Node conectado a cartões de axios e fetch, que levam a um nó de proxy

No Python, configurar um proxy é um único parâmetro proxies na maioria das bibliotecas. No Node.js a coisa é mais confusa: o fetch nativo, o Axios e o node-fetch recebem o proxy por três mecanismos diferentes, o SOCKS5 precisa de um pacote separado e, quando você passa a opção errada para uma biblioteca, muitas vezes nem recebe erro; a requisição sai silenciosamente sem proxy. Por isso é importante saber qual biblioteca lê qual opção.

Neste artigo, vemos com código que funciona as três formas básicas de configurar proxy no Node.js (variável de ambiente, agent e dispatcher), o uso de um proxy com autenticação no fetch nativo, no Axios e no node-fetch, proxies SOCKS5 e a rotação de IP com novas tentativas. Rodamos os exemplos no Node.js 24 contra um proxy de teste local que exige usuário e senha, e anotamos a incompatibilidade de versões que encontramos e como os erros de autenticação aparecem de forma diferente em cada biblioteca.

Formas de configurar proxy no Node.js

O Node.js tem três mecanismos para enviar uma requisição HTTP por um proxy. A biblioteca que você usa define qual mecanismo vale.

  1. Variável de ambiente. As variáveis HTTP_PROXY, HTTPS_PROXY e NO_PROXY. Elas fazem um script inteiro passar pelo proxy sem mudar o código, mas nem toda biblioteca as lê.
  2. Agent. Os módulos clássicos http e https do Node.js abrem conexões por um objeto Agent. O Axios e o node-fetch usam esses módulos, então, quando você passa a eles um agent que se conecta a um proxy (como o HttpsProxyAgent), as requisições passam pelo proxy.
  3. Dispatcher. O fetch nativo do Node.js não usa o módulo clássico http; usa um cliente chamado undici. No undici, o objeto que gerencia as conexões se chama dispatcher, e para proxy você passa um ProxyAgent.

Essa distinção é o ponto mais importante do artigo: o fetch nativo não reconhece a opção agent. Se, por costume do Axios, você escrever fetch(url, { agent }), não recebe erro nenhum e a requisição sai sem proxy.

Explicamos como um proxy funciona em geral e o túnel CONNECT aberto em requisições HTTPS em O que é um servidor proxy e como ele funciona?. Traduzir comandos cURL para fetch e Axios (cabeçalhos, corpo, dados de formulário) é o assunto do nosso artigo cURL em JavaScript; este artigo foca só na parte do proxy.

Como usar proxy no fetch nativo?

ProxyAgent do undici

Instale o pacote undici:

bash
npm install undici

Depois importe fetch e ProxyAgent do mesmo pacote:

javascript
import { fetch, ProxyAgent } from "undici";

const dispatcher = new ProxyAgent("http://user:pass@pr.proxynet.io:8000");

const res = await fetch("https://httpbin.org/ip", {
  dispatcher,
  signal: AbortSignal.timeout(20_000),
});
console.log(res.status, await res.json());

Mesmo que o endereço do proxy comece com http://, você pode acessar sites HTTPS sem problema: o ProxyAgent abre um túnel CONNECT com o proxy, e a conexão TLS com o destino acontece dentro desse túnel.

A armadilha das versões. O Node.js traz a própria cópia do undici; o undici que você instala pelo npm pode ser mais novo. Quando passamos o ProxyAgent do npm ao fetch nativo do Node.js (usando o fetch global sem importar), a requisição falhou com o erro fetch failed no Node.js 24.11 com o undici 8.10; a causa era invalid onRequestStart method. Usar o mesmo agent com o fetch do próprio undici funcionou normalmente. A regra é simples: pegue o fetch do mesmo pacote de onde pegou o ProxyAgent.

Para que todas as chamadas fetch do undici na aplicação usem o mesmo proxy, você pode definir um dispatcher global:

javascript
import { fetch, ProxyAgent, setGlobalDispatcher } from "undici";

setGlobalDispatcher(new ProxyAgent("http://user:pass@pr.proxynet.io:8000"));

const res = await fetch("https://httpbin.org/ip"); // não precisa passar dispatcher

A variável de ambiente NODE_USE_ENV_PROXY

Nas versões atuais do Node.js, o fetch nativo lê as variáveis de ambiente padrão de proxy quando NODE_USE_ENV_PROXY está ativada. Segundo o guia de configuração de rede corporativa do Node.js, o recurso está disponível a partir das versões 22.21.0 e 24.5.0; o mesmo comportamento pode ser ligado com a opção de linha de comando --use-env-proxy.

No Linux e no macOS:

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

No Windows PowerShell:

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

Esse método não exige mudança no código; fetch("https://httpbin.org/ip") passa direto pelo proxy. No nosso teste, quando o mesmo comando rodou sem NODE_USE_ENV_PROXY, a requisição não chegou ao proxy mesmo com HTTPS_PROXY definida. Você pode deixar endereços da rede interna fora do proxy com a variável NO_PROXY.

Se quiser um dispatcher que leia as variáveis de ambiente pelo código, a classe EnvHttpProxyAgent do undici faz o mesmo trabalho:

javascript
import { fetch, EnvHttpProxyAgent } from "undici";

const res = await fetch("https://httpbin.org/ip", { dispatcher: new EnvHttpProxyAgent() });

Como usar proxy no Axios?

No Node.js, o Axios usa os módulos clássicos http e https e oferece duas formas de usar proxy.

A opção proxy nativa

A opção proxy da configuração de requisição do Axios funciona com endereços http:// sem criptografia:

javascript
import axios from "axios";

const { data } = await axios.get("http://httpbin.org/ip", {
  proxy: {
    protocol: "http",
    host: "pr.proxynet.io",
    port: 8000,
    auth: { username: "user", password: "pass" },
  },
  timeout: 20_000,
});
console.log(data);

A senha no campo auth não é codificada; você pode escrever os caracteres especiais como eles são.

https-proxy-agent para endereços HTTPS

Quando o endereço de destino é HTTPS, deixar um agent abrir o túnel dá resultados mais confiáveis:

bash
npm install axios https-proxy-agent
javascript
import axios from "axios";
import { HttpsProxyAgent } from "https-proxy-agent";

const httpsAgent = new HttpsProxyAgent("http://user:pass@pr.proxynet.io:8000");

const client = axios.create({
  httpsAgent,
  proxy: false, // desliga a lógica de proxy própria do Axios e deixa o túnel com o agent
  timeout: 20_000,
});

const { status, data } = await client.get("https://httpbin.org/ip");
console.log(status, data);

Não pule a linha proxy: false. Quando a opção proxy não é informada, o Axios pode tentar aplicar o proxy das variáveis de ambiente com a própria lógica, o que significa dois comportamentos de proxy em conflito junto com o agent. Vincular o agent uma vez com axios.create evita repeti-lo em cada chamada.

Como usar proxy no node-fetch?

Antes de o fetch nativo chegar, o node-fetch era a implementação de fetch mais comum no Node.js, e ainda aparece bastante em projetos antigos. Diferente do fetch nativo, ele usa o módulo clássico http, então o proxy é passado com a opção agent:

bash
npm install node-fetch https-proxy-agent
javascript
import fetch from "node-fetch";
import { HttpsProxyAgent } from "https-proxy-agent";

const agent = new HttpsProxyAgent("http://user:pass@pr.proxynet.io:8000");

const res = await fetch("https://httpbin.org/ip", { agent });
console.log(res.status, await res.json());

A versão 3 do node-fetch é publicada só como módulo ES; se você não consegue carregá-la com import em um projeto CommonJS que usa require, mudar para o fetch nativo geralmente dá menos trabalho. Não há motivo para adicionar o node-fetch a um projeto novo.

Como usar proxy SOCKS5?

O ProxyAgent do undici é para proxies HTTP; o fetch nativo não suporta SOCKS5. Para trabalhar com um proxy SOCKS5, use o pacote socks-proxy-agent com o Axios ou o node-fetch:

bash
npm install socks-proxy-agent
javascript
import axios from "axios";
import { SocksProxyAgent } from "socks-proxy-agent";

// socks5h: o nome de domínio é resolvido do lado do proxy, então nenhuma consulta DNS sai da sua rede
const agent = new SocksProxyAgent("socks5h://user:pass@pr.proxynet.io:1080");

const { data } = await axios.get("https://httpbin.org/ip", {
  httpAgent: agent,
  httpsAgent: agent,
  proxy: false,
});
console.log(data);

No node-fetch, o mesmo agent é passado como fetch(url, { agent }). Nos registros do nosso proxy de teste, as requisições enviadas com o esquema socks5h:// chegaram ao proxy como nome de domínio, não como endereço IP. O esquema socks5:// resolve o nome de domínio no seu computador; os problemas que isso causa estão em Vazamentos de WebRTC e DNS. Antes de escolher entre SOCKS5 e proxy HTTP, veja SOCKS ou HTTP proxy.

Como cada biblioteca recebe o proxy?

ClienteComo passar o proxyVariável de ambienteSOCKS5Erro de autenticação
Fetch nativoNODE_USE_ENV_PROXY ou --use-env-proxySó com a flagNãofetch failed
fetch do undicidispatcher: new ProxyAgent(...)Com EnvHttpProxyAgentNãofetch failed, causa: requisição cancelada
AxiosOpção proxy ou httpsAgent + proxy: falseSe nenhuma opção for passadaCom socks-proxy-agentErro com código de status 407
node-fetchagentNãoCom socks-proxy-agentDepende do agent usado
http.requestagentNãoCom socks-proxy-agentDepende do agent usado

A última coluna importa na depuração. No nosso teste com senha errada, o Axios mostrou a causa com clareza, com a mensagem Request failed with status code 407 e error.response.status === 407. O fetch do undici só deu um erro fetch failed e informou a causa como "Request was cancelled"; a mensagem não menciona autenticação. Se você vir fetch failed com o undici, a primeira coisa a conferir é o usuário e a senha do proxy. Reunimos todas as causas do 407 em Autenticação de proxy: user:pass ou whitelist de IP.

Credenciais e caracteres especiais

Em todos os métodos que recebem o endereço do proxy como URL (ProxyAgent do undici, https-proxy-agent, socks-proxy-agent, variáveis de ambiente), caracteres como @, :, / e # na senha precisam ser codificados. Senão, o endereço é interpretado errado.

javascript
const user = process.env.PROXY_USER;
const pass = encodeURIComponent(process.env.PROXY_PASS);

const proxyUrl = `http://${user}:${pass}@pr.proxynet.io:8000`;

Ler as credenciais de variáveis de ambiente em vez de escrevê-las no código evita que a senha apareça onde o código for compartilhado. No campo proxy.auth do Axios, a senha é escrita sem codificação.

Rotação de IP e novas tentativas

Em um trabalho real de coleta de dados, as requisições falham de vez em quando: o ponto de saída do proxy não alcança o destino, o destino retorna 429 ou 503, ou a conexão dá timeout. O exemplo abaixo escolhe aleatoriamente entre vários proxies, tenta de novo uma requisição que falhou com backoff exponencial e limita, com lotes pequenos, o número de requisições rodando ao mesmo tempo:

javascript
import { fetch, ProxyAgent } from "undici";

const PROXIES = [
  "http://user:pass@pr.proxynet.io:8000",
  "http://user:pass@pr.proxynet.io:8001",
];
const agents = PROXIES.map((p) => new ProxyAgent(p));
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));

async function getWithRetry(url, { attempts = 4, baseMs = 500 } = {}) {
  let lastError;
  for (let i = 0; i < attempts; i++) {
    const dispatcher = agents[Math.floor(Math.random() * agents.length)];
    try {
      const res = await fetch(url, { dispatcher, signal: AbortSignal.timeout(20_000) });
      if (res.status === 429 || res.status >= 500) {
        await res.body?.cancel();
        throw new Error(`HTTP ${res.status}`);
      }
      return await res.text();
    } catch (err) {
      lastError = err;
      await sleep(baseMs * 2 ** i + Math.random() * 250);
    }
  }
  throw lastError;
}

async function crawl(urls, concurrency = 5) {
  const results = [];
  for (let i = 0; i < urls.length; i += concurrency) {
    const batch = urls.slice(i, i + concurrency);
    results.push(...(await Promise.allSettled(batch.map((u) => getWithRetry(u)))));
  }
  return results;
}

const urls = ["https://httpbin.org/ip", "https://httpbin.org/status/503", "https://example.com/"];
const results = await crawl(urls, 2);
results.forEach((r, i) =>
  console.log(urls[i], r.status, r.status === "fulfilled" ? `${r.value.length} bytes` : r.reason.message),
);

Três detalhes do código importam:

  • Promise.allSettled evita que uma requisição com falha em um lote pare as outras. Com Promise.all, um único 503 faria perder os resultados do lote inteiro. No exemplo, o endereço /status/503 foi marcado como rejected depois de quatro tentativas, enquanto os outros endereços retornaram os resultados.
  • res.body?.cancel() libera a conexão sem ler o corpo de uma resposta que vamos tentar de novo. Corpos não lidos podem lotar o pool de conexões.
  • O componente aleatório (Math.random() * 250) evita que requisições que falharam no mesmo momento sejam tentadas de novo ao mesmo tempo e causem um novo acúmulo.

Em vez de gerenciar a lista de proxies você mesmo, se você usar um Proxies rotativos que dá um IP de saída diferente a cada conexão por um único endereço, o array PROXIES fica com um elemento e a rotação acontece do lado do provedor. Para trabalhos em que o mesmo IP precisa ser mantido durante a sessão (páginas logadas, fluxos de várias etapas), prefere-se um Proxies de sessão fixa.

Este exemplo não lê o cabeçalho Retry-After de uma resposta 429. Uma abordagem que respeita o cabeçalho e separa quais códigos de status não devem ser tentados de novo está em Códigos de status HTTP no web scraping. Como o valor de concorrência afeta de fato a velocidade está em Concorrência e paralelismo.

Como verificar se o proxy está funcionando?

A cada configuração nova, verifique se a requisição realmente passa pelo proxy. O jeito mais fácil é enviar uma requisição a um endereço que retorna o IP de saída, primeiro sem proxy e depois com ele:

javascript
import { fetch, ProxyAgent } from "undici";

const ip = async (options = {}) => (await (await fetch("https://api.ipify.org?format=json", options)).json()).ip;

console.log("sem proxy:", await ip());
console.log("com proxy:", await ip({ dispatcher: new ProxyAgent(process.env.HTTPS_PROXY) }));

Se as duas linhas mostrarem o mesmo endereço, a requisição não está passando pelo proxy. A causa mais comum é passar agent para o fetch nativo ou usar um agent no Axios sem escrever proxy: false.

Erros comuns

  • Passar agent para o fetch nativo. A opção é ignorada em silêncio. A chave certa no fetch é dispatcher.
  • Passar o ProxyAgent do undici do npm para o fetch global. Quando as versões não batem, você recebe um erro fetch failed; importe também o fetch do undici.
  • Definir HTTPS_PROXY e supor que o fetch nativo vai ler. Sem NODE_USE_ENV_PROXY ou --use-env-proxy, ele não lê.
  • Não escrever proxy: false com um agent no Axios. Dois mecanismos de proxy entram em conflito.
  • Não codificar os caracteres especiais da senha. O endereço é interpretado errado e a autenticação falha.
  • Resolver DNS localmente com socks5://. Use socks5h:// para resolução remota.
  • Não definir timeout. Uma saída de proxy que não responde deixa uma requisição sem timeout esperando por minutos. Use AbortSignal.timeout no undici e timeout no Axios.
  • Iniciar todas as requisições de uma vez com Promise.all. Sobrecarrega tanto o seu próprio pool de conexões quanto o limite de taxa do site de destino.

Qual método escolher?

Sua situaçãoRecomendação
Projeto novo, poucas dependênciasfetch do undici + ProxyAgent
Passar um script existente pelo proxy sem mudar o códigoNODE_USE_ENV_PROXY=1 + HTTPS_PROXY
O projeto já usa AxiosAxios + https-proxy-agent + proxy: false
Projeto antigo com node-fetchnode-fetch + agent
Proxy SOCKS5Axios ou node-fetch + socks-proxy-agent (socks5h://)
Um IP diferente a cada requisiçãoProxy rotativo, endereço único
O mesmo IP durante a sessãoProxy sticky
Mensagens de erro claras importamAxios (mostra o 407 com o código de status)

Nada disso vale para JavaScript rodando no navegador: o fetch do navegador não consegue mudar o proxy pelo código; o proxy é definido nas configurações do navegador ou do sistema operacional. Para a configuração no Windows, veja Configuração de proxy no Windows e no Chrome.

Perguntas frequentes

O fetch nativo do Node.js suporta proxy?

A partir do Node.js 22.21.0 e 24.5.0, ele lê as variáveis HTTP_PROXY e HTTPS_PROXY quando a variável de ambiente NODE_USE_ENV_PROXY=1 ou a opção --use-env-proxy é usada. Para definir o proxy por requisição pelo código, use o fetch e o ProxyAgent do undici.

O Axios lê a variável de ambiente HTTPS_PROXY?

No Node.js, quando a opção proxy não é informada, o Axios tenta usar o proxy das variáveis de ambiente. Para um comportamento claro e previsível, recomendamos definir o proxy com um agent e escrever proxy: false.

Posso usar um proxy diferente em cada requisição?

Sim. No undici, você pode passar um ProxyAgent diferente para cada chamada fetch, e no Axios um httpsAgent diferente. Em vez de criar agents a cada requisição, crie uma vez e reutilize, como no exemplo acima; cada agent novo abre o próprio pool de conexões.

Como manter os cookies ao usar proxy?

O fetch nativo e o undici não guardam cookies; você precisa ler o cabeçalho Set-Cookie e adicioná-lo à próxima requisição como cabeçalho Cookie. No Axios, dá para usar um cookie jar baseado em tough-cookie. Lembre-se de que, junto com os cookies, o endereço IP também deve se manter durante toda a sessão.

Como passar proxy no Puppeteer ou no Playwright?

Ferramentas de automação de navegador não recebem o proxy como as bibliotecas do Node.js; recebem como opção ao iniciar o navegador. Os agents deste artigo não afetam o navegador. Mostramos o lado do Puppeteer em Puppeteer e CAPTCHA.

Escolho JavaScript ou Python?

Proxies funcionam nas duas linguagens; no Python, a maioria das bibliotecas recebe o proxy com um único parâmetro, enquanto no Node.js o mecanismo muda conforme a biblioteca. A escolha geralmente depende da linguagem da equipe e da estrutura das páginas de destino. Comparamos as duas em Web scraping: JavaScript ou Python?, e para bibliotecas Python veja HTTPX, Requests ou AIOHTTP.

Em resumo

No Node.js, o proxy é passado de três formas diferentes conforme o cliente: um dispatcher do undici ou NODE_USE_ENV_PROXY para o fetch nativo, e um agent para o Axios e o node-fetch. Para destinos HTTPS, use o Axios com https-proxy-agent e proxy: false, e SOCKS5 com socks-proxy-agent e o esquema socks5h://. Pegue ProxyAgent e fetch do mesmo pacote, codifique a senha, defina timeout e rode as requisições em lotes pequenos de Promise.allSettled com lógica de novas tentativas. Planos para o seu trabalho de coleta de dados estão na nossa página de solução de extração de dados.