← Volver al blog

Undici ProxyAgent en Node.js: configuración de proxy HTTP/SOCKS5 y rotación de IP sin dependencias adicionales

Analizamos cómo usar el ProxyAgent integrado undici en Node.js para trabajar con proxies HTTP y SOCKS5, organizar la rotación de IP y prescindir de https-proxy-agent y socks-proxy-agent.

📅18 de octubre de 2026

La mayoría de las guías sobre proxies en Node.js aconsejan incluir en el proyecto https-proxy-agent, socks-proxy-agent y un par de paquetes adicionales. En realidad, desde la versión 18+ de Node.js, el cliente HTTP integrado undici ya incluye ProxyAgent, que cubre la mayoría de las tareas de análisis y prueba de API a través de proxies. En este artículo, veremos cómo configurar la tunelización HTTP/HTTPS a través de undici, organizar la rotación de IP entre solicitudes y qué hacer si necesitas SOCKS5, pero no quieres agregar una dependencia adicional.

¿Por qué undici en lugar de axios y request?

undici es un cliente HTTP/1.1 y HTTP/2, desarrollado por el equipo de Node.js y, a partir de la versión 18, es la base del fetch global. No tiene dependencias transitivas, es más rápido que axios en la mayoría de las pruebas de conexión en pooling, y lo que es importante para nuestro tema, tiene un módulo nativo para trabajar con proxies a través de la tunelización HTTP CONNECT.

Cuando estás analizando Wildberries, Ozon o probando creatividades publicitarias de diferentes geografías, cada paquete adicional en node_modules es un punto de falla potencial al actualizar Node.js. undici viene incluido con el runtime, por lo que obtienes una implementación actualizada de TLS, HTTP/2 y keep-alive sin el riesgo de que una biblioteca abandonada se rompa en una nueva versión mayor.

La instalación es muy sencilla:

npm install undici

Si tienes Node.js 20+, parte de la API está disponible globalmente sin importar, pero para ProxyAgent aún necesitas importar, ya que no se exporta en el fetch global por defecto.

Configuración básica de ProxyAgent

Un ejemplo mínimo de trabajo de una solicitud a través de un proxy se ve así:

import { ProxyAgent, fetch } from 'undici';

const proxyAgent = new ProxyAgent('http://proxy.example.com:8080');

const response = await fetch('https://httpbin.org/ip', {
  dispatcher: proxyAgent,
});

const data = await response.json();
console.log(data);

El punto clave es el parámetro dispatcher. No es una opción agent, como en el antiguo módulo http, sino el dispatcher de undici, que intercepta la conexión antes del apretón de manos TLS y establece un túnel a través del método CONNECT al host de destino. Para solicitudes HTTPS a través de un proxy, esto es crítico: un proxy HTTP normal no ve el contenido del tráfico cifrado, simplemente pasa los bytes dentro del túnel TCP.

Si trabajas no con fetch, sino con el bajo nivel undici.request, la sintaxis es similar:

import { request, ProxyAgent } from 'undici';

const proxyAgent = new ProxyAgent('http://proxy.example.com:8080');

const { statusCode, body } = await request('https://api.example.com/data', {
  dispatcher: proxyAgent,
});

console.log(statusCode);
for await (const chunk of body) {
  console.log(chunk.toString());
}

Para parsers de producción que hacen cientos de solicitudes por minuto, es importante no crear un nuevo ProxyAgent en cada solicitud, ya que esto arruina el pooling de conexiones. Crea una instancia del agente una vez para el pool de conexiones y reutilízala entre las llamadas.

Autenticación de proxy con nombre de usuario y contraseña

La mayoría de los proveedores de proxies de pago ofrecen acceso mediante un par de nombre de usuario/contraseña, en lugar de IP-whitelisting. En undici, las credenciales se pasan directamente en la URL del proxy:

import { ProxyAgent, fetch } from 'undici';

const login = 'user123';
const password = 'pass456';
const proxyUrl = `http://${login}:${password}@gate.proxycove.com:9000`;

const proxyAgent = new ProxyAgent(proxyUrl);

const response = await fetch('https://httpbin.org/ip', {
  dispatcher: proxyAgent,
});

console.log(await response.json());

Ten en cuenta: si el nombre de usuario o la contraseña contienen caracteres especiales (@, :, /), deben ser codificados con encodeURIComponent, de lo contrario, el parser de URL dentro de undici no descompondrá correctamente la cadena de conexión y recibirás un error de autenticación 407 en lugar de un mensaje comprensible.

Para tareas donde la estabilidad de la sesión es importante — por ejemplo, autorización en un marketplace vinculada a una IP durante la sesión — es más conveniente usar proxies residenciales con sesión fija: la IP no cambia durante un tiempo determinado, lo que significa que el servicio no ve "saltos" entre geografías y no requiere un captcha repetido en cada solicitud.

Rotación de proxies entre solicitudes

Si tienes una lista de N proxies y la tarea es distribuir las solicitudes entre ellos, la opción más sencilla es round-robin sin bibliotecas externas para colas:

import { ProxyAgent, fetch } from 'undici';

const proxyList = [
  'http://user:[email protected]:8000',
  'http://user:[email protected]:8000',
  'http://user:[email protected]:8000',
];

let currentIndex = 0;

function getNextAgent() {
  const proxyUrl = proxyList[currentIndex];
  currentIndex = (currentIndex + 1) % proxyList.length;
  return new ProxyAgent(proxyUrl);
}

async function fetchWithRotation(url) {
  const agent = getNextAgent();
  return fetch(url, { dispatcher: agent });
}

for (let i = 0; i < 10; i++) {
  const res = await fetchWithRotation('https://httpbin.org/ip');
  console.log(await res.json());
}

Este enfoque funciona bien para analizar tarjetas de productos o monitorear precios, donde cada solicitud siguiente es lógicamente independiente de la anterior. Para tareas donde se necesita una alta velocidad de cambio de IP en cada solicitud sin gestión manual de la lista, es más fácil usar proxies con rotación automática del lado del proveedor — así, todo el código anterior se reduce a una sola URL estática del gateway, y la rotación es manejada por un proxy de centro de datos o un pool residencial con rotación por temporizador.

Si necesitas no solo rotación circular, sino ponderada — por ejemplo, dar prioridad a proxies más rápidos — agrega peso a la estructura de datos:

const weightedProxies = [
  { url: 'http://user:[email protected]:8000', weight: 5 },
  { url: 'http://user:[email protected]:8000', weight: 1 },
];

function pickWeighted(list) {
  const total = list.reduce((sum, p) => sum + p.weight, 0);
  let rnd = Math.random() * total;
  for (const p of list) {
    if (rnd < p.weight) return p.url;
    rnd -= p.weight;
  }
  return list[0].url;
}

Verificación de la disponibilidad de proxies y exclusión de IP "muertas"

Un pool de proxies se "estropea" con el tiempo: algunas IP son bloqueadas por el sitio objetivo, otras dejan de responder. Para que la rotación no envíe solicitudes a direcciones muertas, agrega una verificación de salud con un timeout a través del AbortController integrado:

import { ProxyAgent, fetch } from 'undici';

async function isProxyAlive(proxyUrl, timeoutMs = 5000) {
  const agent = new ProxyAgent(proxyUrl);
  const controller = new AbortController();
  const timer = setTimeout(() => controller.abort(), timeoutMs);

  try {
    const res = await fetch('https://httpbin.org/ip', {
      dispatcher: agent,
      signal: controller.signal,
    });
    return res.status === 200;
  } catch {
    return false;
  } finally {
    clearTimeout(timer);
  }
}

async function filterAliveProxies(proxyList) {
  const checks = await Promise.all(
    proxyList.map(async (url) => ({ url, alive: await isProxyAlive(url) }))
  );
  return checks.filter((c) => c.alive).map((c) => c.url);
}

Ejecuta esta verificación cada pocos minutos con un temporizador en segundo plano y actualiza el pool activo, no toda la lista estática. Esto es especialmente importante si mantienes tus propios proxies en IP de centros de datos baratos — allí el porcentaje de fallos es mayor que en pools administrados por proveedores.

SOCKS5 sin bibliotecas externas

Una advertencia importante: el ProxyAgent integrado en undici funciona a través de la tunelización HTTP CONNECT y "de fábrica" solo entiende proxies HTTP/HTTPS. El protocolo SOCKS5 es diferente — es un apretón de manos binario a nivel TCP, y undici no lo analiza de forma nativa. Si necesitas SOCKS5 sin instalar socks-proxy-agent, puedes implementar un conector mínimo manualmente a través del módulo net y la opción connect en un Agent personalizado:

import net from 'node:net';
import { Agent, fetch } from 'undici';

function socksHandshake(socket, targetHost, targetPort) {
  return new Promise((resolve, reject) => {
    // Paso 1: saludo, método sin autenticación
    socket.write(Buffer.from([0x05, 0x01, 0x00]));

    socket.once('data', (greeting) => {
      if (greeting[1] !== 0x00) {
        return reject(new Error('SOCKS5: método de autenticación no soportado'));
      }

      const hostBuf = Buffer.from(targetHost, 'utf8');
      const request = Buffer.concat([
        Buffer.from([0x05, 0x01, 0x00, 0x03, hostBuf.length]),
        hostBuf,
        Buffer.from([targetPort >> 8, targetPort & 0xff]),
      ]);

      socket.write(request);

      socket.once('data', (reply) => {
        if (reply[1] !== 0x00) {
          return reject(new Error('SOCKS5: conexión rechazada'));
        }
        resolve(socket);
      });
    });
  });
}

const socksAgent = new Agent({
  connect: (options, callback) => {
    const socket = net.connect(1080, 'socks-proxy.example.com', async () => {
      try {
        await socksHandshake(socket, options.hostname, 443);
        callback(null, socket);
      } catch (err) {
        callback(err, null);
      }
    });
    socket.on('error', (err) => callback(err, null));
  },
});

const response = await fetch('https://httpbin.org/ip', { dispatcher: socksAgent });
console.log(await response.json());

Este es un apretón de manos simplificado sin autenticación por nombre de usuario y contraseña (método 0x02) y sin soporte para IPv6 — para producción, deberías agregar una rama con autenticación y manejo de diferentes tipos de direcciones. Pero para entender la mecánica, esto es suficiente: SOCKS5 no requiere TLS dentro del túnel en la etapa de apretón de manos, a diferencia de HTTP CONNECT, por lo que el código es más corto de lo que parece a primera vista.

En la práctica, SOCKS5 se utiliza más a menudo no para analizar páginas web, sino para redirigir el tráfico de aplicaciones móviles y navegadores anti-detección como Dolphin Anty o AdsPower, donde el protocolo es necesario para trabajar a nivel de sockets, no de solicitudes HTTP. Si tu tarea es tráfico HTTP/HTTPS en Node.js, el ProxyAgent integrado suele ser suficiente, y no tiene sentido lidiar con un conector SOCKS5 manual.

Manejo de timeouts y lógica de reintentos

Los proxies interrumpen las conexiones más a menudo que una conexión directa — esto es normal para redes residenciales, donde un nodo puede irse offline en cualquier momento. Necesitas un envoltorio de reintentos con un retraso exponencial:

import { ProxyAgent, fetch } from 'undici';

async function fetchWithRetry(url, proxyList, maxRetries = 3) {
  let lastError;

  for (let attempt = 0; attempt < maxRetries; attempt++) {
    const proxyUrl = proxyList[attempt % proxyList.length];
    const agent = new ProxyAgent(proxyUrl);
    const controller = new AbortController();
    const timer = setTimeout(() => controller.abort(), 8000);

    try {
      const res = await fetch(url, { dispatcher: agent, signal: controller.signal });
      clearTimeout(timer);
      if (res.status >= 500) throw new Error(`Error del servidor: ${res.status}`);
      return res;
    } catch (err) {
      clearTimeout(timer);
      lastError = err;
      const delay = 500 * 2 ** attempt;
      await new Promise((r) => setTimeout(r, delay));
    }
  }

  throw new Error(`Se han agotado todos los intentos: ${lastError?.message}`);
}

Este envoltorio también resuelve el problema de rotación en caso de errores: si el proxy actual falla, el siguiente intento se envía automáticamente a otra IP de la lista, en lugar de seguir intentando en el mismo nodo muerto.

Comparación de undici ProxyAgent, https-proxy-agent y socks-proxy-agent

Herramienta Dependencias Soporte SOCKS5 HTTP/2 Velocidad
undici ProxyAgent ninguna (integrado en Node 18+) no de fábrica sí alta
https-proxy-agent 1-2 paquetes no no media
socks-proxy-agent 3-4 paquetes sí no media

Para un análisis limpio de HTTP/HTTPS con el mínimo número de dependencias, undici ProxyAgent gana por su integración y compatibilidad con HTTP/2. Para SOCKS5 en producción, es más fácil usar el probado socks-proxy-agent que mantener un apretón de manos personalizado — la implementación manual de la sección anterior es buena para el aprendizaje y casos extremos raros, pero no para producción cargada con miles de conexiones por hora.

Casos prácticos: análisis y pruebas geográficas

Monitoreo de precios en marketplaces. Un script en undici con rotación de proxies y lógica de reintentos de las secciones anteriores es ideal para eludir el rate-limit en Wildberries y Ozon: cada solicitud siguiente de la tarjeta de producto sale con una nueva IP, y la verificación de salud excluye automáticamente las direcciones bloqueadas del pool.

Pruebas de creatividades publicitarias por geografía. Si necesitas verificar cómo se entrega el contenido a un usuario de un país específico para Google Ads o Facebook Ads, simplemente cambia la URL del proxy en ProxyAgent al nodo de la región deseada — no es necesario levantar ningún navegador si la verificación se realiza a través de la API o la respuesta HTML del servidor.

Verificación de la disponibilidad de contenido en diferentes países. Para tareas donde el sitio entrega contenido diferente según la IP, las conexiones residenciales parecen para el servidor objetivo indistinguibles de un usuario normal, lo que reduce el riesgo de bloqueo en comparación con los rangos de IP de centros de datos. Este escenario es especialmente relevante para proxies móviles, si el servicio objetivo verifica el tipo de red y bloquea rangos de centros de datos evidentes.

Verificación masiva de endpoints de API. Al realizar pruebas de carga en tu propia API a través de un pool de proxies externos, es importante prestar atención al pooling de conexiones — undici reutiliza por defecto las conexiones TCP dentro de un ProxyAgent, lo que ahorra tiempo en los apretón de manos TLS al hacer cientos de solicitudes consecutivas a un mismo host.

Conclusión

El ProxyAgent integrado de undici cubre la mayoría de las tareas con proxies HTTP/HTTPS en Node.js sin necesidad de arrastrar paquetes externos: la autenticación a través de la URL, la tunelización a través de CONNECT, la compatibilidad con HTTP/2 y el pooling de conexiones funcionan de inmediato. La rotación y la verificación de salud de los proxies se pueden construir fácilmente con promesas limpias y temporizadores, y SOCKS5 se puede implementar manualmente a través de un conector connect personalizado si es necesario, aunque para producción es más sensato usar una biblioteca lista específicamente para SOCKS.

Si tu script en undici está destinado a analizar marketplaces o monitorear precios, considera usar proxies residenciales — son más resistentes a los sistemas anti-bot que las clásicas IP de centros de datos y requieren menos soluciones adicionales de captcha en solicitudes masivas a Wildberries, Ozon o Avito.