---
title: "Cómo usar un proxy en Node.js: Axios y node-fetch"
description: "Cómo usar un proxy en Node.js con Axios, node-fetch y fetch integrado, con código que funciona. Incluye autenticación, SOCKS5, rotación y errores."
url: https://proxynet.io/es/blog/nodejs-proxy
date: 2026-09-13
author: "Acar Diveroli"
category: "Tutoriales, Integración"
lang: es
---

# Cómo usar un proxy en Node.js: Axios y node-fetch

En Python, configurar un proxy es un solo parámetro `proxies` en la mayoría de las librerías. En Node.js es más enrevesado: el `fetch` integrado, Axios y node-fetch reciben el proxy por tres mecanismos distintos, SOCKS5 necesita un paquete aparte y, cuando das a una librería la opción equivocada, a menudo ni siquiera recibes un error; la petición sale en silencio sin proxy. Por eso importa saber qué librería lee qué opción.

En este artículo vemos, con código que funciona, las tres formas básicas de configurar un proxy en Node.js (variable de entorno, agent y dispatcher), el uso de un proxy con autenticación con el fetch integrado, Axios y node-fetch, los proxies SOCKS5 y la rotación de IP con reintentos. Ejecutamos los ejemplos en Node.js 24 contra un proxy de prueba local que exige usuario y contraseña, y anotamos la incompatibilidad de versiones que encontramos y cómo se ven de forma distinta los errores de autenticación según la librería.

> **Nota: Respuesta breve**
>
> Para el fetch integrado, usa juntos el `fetch` y el `ProxyAgent` de undici, o inicia Node.js con `NODE_USE_ENV_PROXY=1` y la variable de entorno `HTTPS_PROXY`. En Axios, para direcciones HTTPS, pasa como `httpsAgent` un agent creado con `https-proxy-agent` y pon `proxy: false`. En node-fetch, el mismo agent va en la opción `agent`. Para SOCKS5, usa el paquete `socks-proxy-agent`.

## Formas de configurar un proxy en Node.js

Node.js tiene tres mecanismos para enviar una petición HTTP a través de un proxy. La librería que uses decide qué mecanismo se aplica.

1. **Variable de entorno.** Las variables `HTTP_PROXY`, `HTTPS_PROXY` y `NO_PROXY`. Enrutan un script entero por el proxy sin cambiar el código, pero no todas las librerías las leen.
2. **Agent.** Los módulos clásicos `http` y `https` de Node.js abren conexiones a través de un objeto `Agent`. Axios y node-fetch usan estos módulos, así que cuando les das un agent que se conecta a un proxy (como `HttpsProxyAgent`), las peticiones pasan por el proxy.
3. **Dispatcher.** El `fetch` integrado de Node.js no usa el módulo clásico `http`; usa un cliente llamado undici. En undici, el objeto que gestiona las conexiones se llama `dispatcher`, y para un proxy le das un `ProxyAgent`.

Esta distinción es el punto más importante del artículo: **el fetch integrado no reconoce la opción `agent`.** Si por costumbre de Axios escribes `fetch(url, { agent })`, no recibes ningún error y la petición sale sin proxy.

Explicamos cómo funciona un proxy en general y el túnel `CONNECT` que se establece para peticiones HTTPS en [Qué es un servidor proxy y cómo funciona](/es/blog/what-is-a-proxy-server). Traducir comandos cURL a fetch y Axios (cabeceras, cuerpo, datos de formulario) es el tema de nuestro artículo [cURL en JavaScript](/es/blog/curl-in-javascript); este artículo se centra solo en la parte del proxy.

## ¿Cómo se usa un proxy con el fetch integrado?

### ProxyAgent de undici

Instala el paquete undici:

```bash
npm install undici
```

Después importa `fetch` y `ProxyAgent` **del mismo paquete**:

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

Aunque la dirección del proxy empiece por `http://`, puedes llegar sin problema a sitios HTTPS: `ProxyAgent` abre un túnel `CONNECT` con el proxy y la conexión TLS con el destino se hace dentro de ese túnel.

**La trampa de las versiones.** Node.js incluye su propia copia de undici; la undici que instalas desde npm puede ser más nueva. Cuando pasamos el `ProxyAgent` de npm al `fetch` **integrado** de Node.js (usando el `fetch` global sin importarlo), la petición falló con un error `fetch failed` en Node.js 24.11 con undici 8.10; la causa era `invalid onRequestStart method`. Usar el mismo agent con el `fetch` propio de undici funcionó sin problemas. La regla es sencilla: toma `fetch` del mismo paquete del que tomaste `ProxyAgent`.

Para que todas las llamadas `fetch` de undici de la aplicación usen el mismo proxy, puedes definir un 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"); // no hace falta pasar dispatcher
```

### La variable de entorno NODE_USE_ENV_PROXY

En las versiones actuales de Node.js, el fetch integrado lee las variables de entorno estándar de proxy cuando `NODE_USE_ENV_PROXY` está activada. Según la [guía de configuración de red empresarial](https://nodejs.org/learn/http/enterprise-network-configuration) de Node.js, la función está disponible desde las versiones 22.21.0 y 24.5.0; el mismo comportamiento se activa con la opción de línea de comandos `--use-env-proxy`.

En Linux y macOS:

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

En Windows PowerShell:

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

Este método no requiere cambios en el código; `fetch("https://httpbin.org/ip")` pasa directamente por el proxy. En nuestra prueba, cuando ejecutamos el mismo comando sin `NODE_USE_ENV_PROXY`, la petición no llegó al proxy aunque `HTTPS_PROXY` estaba definida. Puedes dejar las direcciones de la red interna fuera del proxy con la variable `NO_PROXY`.

Si quieres un dispatcher que lea las variables de entorno desde el código, la clase `EnvHttpProxyAgent` de undici hace lo mismo:

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

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

## ¿Cómo se usa un proxy con Axios?

Axios usa los módulos clásicos `http` y `https` en Node.js y ofrece dos formas de usar un proxy.

### La opción proxy integrada

La opción `proxy` de la [configuración de peticiones](https://axios-http.com/docs/req_config) de Axios funciona con direcciones `http://` sin cifrar:

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

La contraseña del campo `auth` no se codifica; puedes escribir los caracteres especiales tal cual.

### https-proxy-agent para direcciones HTTPS

Cuando la dirección de destino es HTTPS, dejar que un agent establezca el túnel da resultados más fiables:

```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, // desactiva la lógica de proxy propia de Axios y deja el túnel al agent
  timeout: 20_000,
});

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

No te saltes la línea `proxy: false`. Si no se indica la opción `proxy`, Axios puede intentar aplicar con su propia lógica el proxy de las variables de entorno, lo que supone dos comportamientos de proxy en conflicto junto al agent. Vincular el agent una sola vez con `axios.create` te evita repetirlo en cada llamada.

## ¿Cómo se usa un proxy con node-fetch?

Antes de que llegara el fetch integrado, node-fetch era la implementación de fetch más común en Node.js, y todavía aparece a menudo en proyectos antiguos. A diferencia del fetch integrado, usa el módulo clásico `http`, así que el proxy se pasa con la opción `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());
```

La versión 3 de node-fetch se publica solo como módulo ES; si no puedes cargarla con `import` en un proyecto CommonJS que usa `require`, pasar al fetch integrado suele dar menos trabajo. No hay motivo para añadir node-fetch a un proyecto nuevo.

## ¿Cómo se usa un proxy SOCKS5?

El `ProxyAgent` de undici es para proxies HTTP; el fetch integrado no admite SOCKS5. Para trabajar con un proxy SOCKS5, usa el paquete `socks-proxy-agent` con Axios o node-fetch:

```bash
npm install socks-proxy-agent
```

```javascript
import axios from "axios";
import { SocksProxyAgent } from "socks-proxy-agent";

// socks5h: el nombre de dominio se resuelve en el lado del proxy, así que ninguna consulta DNS sale de tu red
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);
```

En node-fetch, el mismo agent se pasa como `fetch(url, { agent })`. En los registros de nuestro proxy de prueba, las peticiones enviadas con el esquema `socks5h://` llegaron al proxy como nombre de dominio, no como dirección IP. El esquema `socks5://` resuelve el nombre de dominio en tu equipo; los problemas que eso causa se explican en [Fugas de WebRTC y DNS](/es/blog/webrtc-dns-leak). Antes de elegir entre SOCKS5 y un proxy HTTP, consulta [SOCKS o HTTP](/es/blog/socks-vs-http-proxy).

## ¿Cómo recibe el proxy cada librería?

| Cliente | Cómo pasar el proxy | Variable de entorno | SOCKS5 | Error de autenticación |
|---|---|---|---|---|
| Fetch integrado | `NODE_USE_ENV_PROXY` o `--use-env-proxy` | Solo con el flag | No | `fetch failed` |
| `fetch` de undici | `dispatcher: new ProxyAgent(...)` | Con `EnvHttpProxyAgent` | No | `fetch failed`, causa: petición cancelada |
| Axios | Opción `proxy` o `httpsAgent` + `proxy: false` | Si no se indica opción | Con `socks-proxy-agent` | Error con código de estado `407` |
| node-fetch | `agent` | No | Con `socks-proxy-agent` | Depende del agent usado |
| `http.request` | `agent` | No | Con `socks-proxy-agent` | Depende del agent usado |

La última columna importa al depurar. En nuestra prueba con una contraseña incorrecta, Axios mostró la causa con claridad con el mensaje `Request failed with status code 407` y `error.response.status === 407`. El `fetch` de undici solo dio un error `fetch failed` e indicó como causa «Request was cancelled»; el mensaje no menciona la autenticación. Si ves `fetch failed` con undici, lo primero que debes comprobar es el usuario y la contraseña del proxy. Reunimos todas las causas del `407` en [Autenticación de proxy: user:pass o lista blanca de IP](/es/blog/proxy-authentication-methods).

## Credenciales y caracteres especiales

En todos los métodos que reciben la dirección del proxy como URL (`ProxyAgent` de undici, `https-proxy-agent`, `socks-proxy-agent`, variables de entorno), los caracteres como `@`, `:`, `/` y `#` de la contraseña deben codificarse. Si no, la dirección se interpreta mal.

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

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

Leer las credenciales de variables de entorno en lugar de escribirlas en el código evita que la contraseña aparezca allí donde se comparta el código. En el campo `proxy.auth` de Axios, la contraseña se escribe sin codificar.

## Rotación de IP y reintentos

En un trabajo real de recogida de datos, las peticiones fallan de vez en cuando: el punto de salida del proxy no llega al destino, el destino devuelve `429` o `503`, o la conexión da timeout. El ejemplo siguiente elige al azar entre varios proxies, reintenta una petición fallida con backoff exponencial y limita con lotes pequeños el número de peticiones que se ejecutan a la vez:

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

Hay tres detalles del código que importan:

- **`Promise.allSettled`** evita que una petición fallida de un lote detenga a las demás. Con `Promise.all`, un solo `503` haría perder los resultados de todo el lote. En el ejemplo, la dirección `/status/503` quedó marcada como `rejected` tras cuatro intentos, mientras las demás devolvieron sus resultados.
- **`res.body?.cancel()`** libera la conexión sin leer el cuerpo de una respuesta que vamos a reintentar. Los cuerpos sin leer pueden llenar el pool de conexiones.
- **El componente aleatorio** (`Math.random() * 250`) evita que las peticiones que fallaron en el mismo momento se reintenten a la vez y provoquen una nueva acumulación.

En lugar de gestionar tú la lista de proxies, si usas un [Proxies rotativos](https://proxynet.io/es/rotating-proxy) que da una IP de salida distinta en cada conexión a través de una sola dirección, el array `PROXIES` se reduce a un elemento y la rotación ocurre en el lado del proveedor. Para trabajos en los que hay que mantener la misma IP durante la sesión (páginas con sesión iniciada, flujos de varios pasos), se prefiere un [Proxies de sesión fija](https://proxynet.io/es/sticky-proxy).

Este ejemplo no lee la cabecera `Retry-After` de una respuesta `429`. Un enfoque que respeta la cabecera y separa qué códigos de estado no deben reintentarse se explica en [Códigos de estado HTTP en web scraping](/es/blog/http-status-codes-web-scraping). Cómo afecta realmente el valor de concurrencia a la velocidad se trata en [Concurrencia y paralelismo](/es/blog/concurrency-vs-parallelism).

## ¿Cómo se comprueba que el proxy funciona?

Comprueba con cada configuración nueva que la petición pasa realmente por el proxy. La forma más sencilla es enviar una petición a una dirección que devuelve la IP de salida, primero sin proxy y luego con él:

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

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

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

Si ambas líneas muestran la misma dirección, la petición no pasa por el proxy. La causa más común es pasar `agent` al fetch integrado, o usar un agent en Axios sin escribir `proxy: false`.

## Errores comunes

- **Pasar `agent` al fetch integrado.** La opción se ignora en silencio. La clave correcta para fetch es `dispatcher`.
- **Pasar el `ProxyAgent` de undici de npm al fetch global.** Cuando las versiones no coinciden recibes un error `fetch failed`; importa también `fetch` desde undici.
- **Definir `HTTPS_PROXY` y suponer que el fetch integrado la leerá.** Sin `NODE_USE_ENV_PROXY` o `--use-env-proxy`, no la lee.
- **No escribir `proxy: false` con un agent en Axios.** Dos mecanismos de proxy entran en conflicto.
- **No codificar los caracteres especiales de la contraseña.** La dirección se interpreta mal y la autenticación falla.
- **Resolver el DNS localmente con `socks5://`.** Usa `socks5h://` para la resolución remota.
- **No poner timeout.** Una salida de proxy que no responde deja esperando minutos una petición sin timeout. Usa `AbortSignal.timeout` en undici y `timeout` en Axios.
- **Iniciar todas las peticiones a la vez con `Promise.all`.** Satura tanto tu propio pool de conexiones como el límite de velocidad del sitio de destino.

## ¿Qué método conviene elegir?

| Tu situación | Recomendación |
|---|---|
| Proyecto nuevo, pocas dependencias | `fetch` de undici + `ProxyAgent` |
| Enrutar un script existente por un proxy sin cambiar el código | `NODE_USE_ENV_PROXY=1` + `HTTPS_PROXY` |
| El proyecto ya usa Axios | Axios + `https-proxy-agent` + `proxy: false` |
| Proyecto antiguo con node-fetch | node-fetch + `agent` |
| Proxy SOCKS5 | Axios o node-fetch + `socks-proxy-agent` (`socks5h://`) |
| Una IP distinta en cada petición | Proxy rotativo, una sola dirección |
| La misma IP durante la sesión | Proxy de sesión fija |
| Importan los mensajes de error claros | Axios (muestra el 407 con el código de estado) |

Nada de esto se aplica a JavaScript que se ejecuta en el navegador: el fetch del navegador no puede cambiar el proxy desde el código; el proxy se configura en los ajustes del navegador o del sistema operativo. Para la configuración en Windows, consulta [Configuración de proxy en Windows y Chrome](/es/blog/windows-chrome-proxy-settings).

## Preguntas frecuentes

### ¿El fetch integrado de Node.js admite proxies?

Desde Node.js 22.21.0 y 24.5.0, lee las variables `HTTP_PROXY` y `HTTPS_PROXY` cuando se usa la variable de entorno `NODE_USE_ENV_PROXY=1` o la opción `--use-env-proxy`. Para definir un proxy por petición desde el código, usa el `fetch` y el `ProxyAgent` de undici.

### ¿Axios lee la variable de entorno HTTPS_PROXY?

En Node.js, cuando no se indica la opción `proxy`, Axios intenta usar el proxy de las variables de entorno. Para un comportamiento claro y predecible, recomendamos definir el proxy con un agent y escribir `proxy: false`.

### ¿Puedo usar un proxy distinto en cada petición?

Sí. En undici puedes dar a cada llamada `fetch` un `ProxyAgent` distinto, y en Axios un `httpsAgent` distinto. En lugar de crear agents en cada petición, créalos una vez y reutilízalos como en el ejemplo anterior; cada agent nuevo abre su propio pool de conexiones.

### ¿Cómo se mantienen las cookies al usar un proxy?

El fetch integrado y undici no guardan cookies; tienes que leer la cabecera `Set-Cookie` y añadirla a la petición siguiente como cabecera `Cookie`. En Axios puedes usar un cookie jar basado en `tough-cookie`. Recuerda que, junto con las cookies, la dirección IP también debe mantenerse durante toda la sesión.

### ¿Cómo se pasa un proxy en Puppeteer o Playwright?

Las herramientas de automatización de navegador no reciben el proxy como las librerías de Node.js; lo reciben como opción al iniciar el navegador. Los agents de este artículo no afectan al navegador.

### ¿Elijo JavaScript o Python?

Los proxies funcionan en ambos lenguajes; en Python la mayoría de las librerías reciben el proxy con un solo parámetro, mientras que en Node.js el mecanismo cambia según la librería. La elección suele depender del lenguaje del equipo y de la estructura de las páginas de destino. Los comparamos en [Web scraping: ¿JavaScript o Python?](/es/blog/web-scraping-javascript-vs-python).

## En resumen

En Node.js, el proxy se pasa de tres formas distintas según el cliente: un `dispatcher` de undici o `NODE_USE_ENV_PROXY` para el fetch integrado, y un `agent` para Axios y node-fetch. Para destinos HTTPS, usa Axios con `https-proxy-agent` y `proxy: false`, y SOCKS5 con `socks-proxy-agent` y el esquema `socks5h://`. Toma `ProxyAgent` y `fetch` del mismo paquete, codifica la contraseña, pon un timeout y ejecuta las peticiones en lotes pequeños de `Promise.allSettled` con lógica de reintentos. Encontrarás planes para tu trabajo de recogida de datos en nuestra página de [solución de extracción de datos](/es/data-scraping).
