Conocimiento

Cómo usar proxies residenciales en Node.js con Axios y Got

Proxies en Node.js: por qué Axios necesita https-proxy-agent y proxy:false, la opción agent de Got, el ProxyAgent de undici para fetch, y la rotación geo por petición.

Chris Collins

Chris Collins

29 de julio de 2026 · 9 min de lectura

Node.js es el runtime por defecto para una enorme cantidad de trabajo de scraping y automatización: un event loop que se encoge de hombros ante miles de peticiones concurrentes, un ecosistema de paquetes masivo, y el mismo lenguaje de principio a fin. Conectar un proxy residencial a él son unas pocas líneas, pero los detalles hacen tropezar a la gente de un modo específico de Node, porque el cliente HTTP más popular, Axios, tiene una opción de proxy que no hace lo que esperas sobre HTTPS. Acierta con el agent y el resto es fácil.

Esta es la entrada de Node.js de la misma serie que proxies residenciales con Python, con Playwright y con Go: el código que funciona para Axios, Got y el fetch nativo, más las trampas propias del ecosistema de Node.

Todo lo de abajo usa el gateway residencial de Shifter: un endpoint, p.shifter.io:443, con todo el targeting codificado en el nombre de usuario. Cambia el host y las credenciales por otro proveedor; la forma es la misma.

El modelo del gateway en un párrafo

El nombre de usuario del proxy lleva tu autenticación y tu targeting. No cambias de endpoint para cambiar de país o de sesión, cambias la cadena del nombre de usuario:

customer-USERNAME-country-us-sid-abc123-ttl-600

country-us apunta a Estados Unidos, sid fija una sesión sticky, ttl mantiene esa IP durante N segundos. Omite sid/ttl y cada conexión nueva rota. La contraseña se mantiene constante. En Node, esa cadena entera va dentro de la URL del proxy que le pasas a un proxy agent.

Axios: usa un agent, no la opción proxy incorporada

Aquí está lo más importante de este post. Axios tiene una opción proxy, y para destinos HTTPS con autenticación es poco fiable, no abre un túnel CONNECT como es debido y falla en silencio o filtra tu IP real. La solución en la que el ecosistema se asentó es pasarle a Axios un httpsAgent construido con https-proxy-agent, y poner proxy: false para que Axios no intente manejar el proxy por su cuenta.

import axios from 'axios';
import { HttpsProxyAgent } from 'https-proxy-agent';
const user = `${process.env.SHIFTER_USER}-country-us`;
const pass = process.env.SHIFTER_PASS;
const proxyUrl = `http://${user}:${pass}@p.shifter.io:443`;
const agent = new HttpsProxyAgent(proxyUrl);
const client = axios.create({
httpsAgent: agent,
proxy: false, // crítico: deja que el agent lo maneje, no Axios
timeout: 30000,
});
const res = await client.get('https://api.ipify.org');
console.log(res.data); // una IP residencial de EE. UU.

Dos cosas que interiorizar. La cadena user incluye los flags de targeting (-country-us), porque ahí es donde vive la geo. Y proxy: false no es opcional, sin él la propia lógica de proxy de Axios choca con el agent y obtienes justo el comportamiento roto que intentabas evitar. Esta única línea es el bug de proxy en Node más común.

Got: el agent va en la ranura agent.https

Got toma el proxy agent mediante su opción agent, indexada por protocolo. El mismo https-proxy-agent por debajo, sin el baile de proxy: false porque Got no tiene manejo de proxy incorporado con el que pelear.

import got from 'got';
import { HttpsProxyAgent } from 'https-proxy-agent';
const proxyUrl = `http://${user}:${pass}@p.shifter.io:443`;
const res = await got('https://api.ipify.org', {
agent: { https: new HttpsProxyAgent(proxyUrl) },
timeout: { request: 30000 },
});
console.log(res.body); // una IP residencial de EE. UU.

Una nota de empaquetado: Got es ESM puro desde la v12, así que import got from 'got' necesita un proyecto ESM ("type": "module" en package.json) o un import() dinámico. Si estás atascado en CommonJS, o te quedas en Got v11 o cambias a Axios/undici. Esta división ESM-vs-CommonJS es un tropiezo específico de Node, no un problema de proxy, pero muerde a quien lo monta por primera vez.

Fetch nativo: el ProxyAgent de undici como dispatcher

Node 18+ trae un fetch global respaldado por undici, y undici tiene su propio soporte de proxy que no usa https-proxy-agent en absoluto. Pasas un ProxyAgent como el dispatcher de la petición:

import { ProxyAgent } from 'undici';
const dispatcher = new ProxyAgent(`http://${user}:${pass}@p.shifter.io:443`);
const res = await fetch('https://api.ipify.org', { dispatcher });
console.log(await res.text()); // una IP residencial de EE. UU.

Para enrutar cada fetch del proceso a través del proxy, ponlo en global:

import { setGlobalDispatcher, ProxyAgent } from 'undici';
setGlobalDispatcher(new ProxyAgent(proxyUrl));

Si estás en Node moderno y quieres cero dependencias de cliente HTTP, este es el camino más limpio.

Trampa 1: reutiliza el agent, no construyas uno por petición

Sea cual sea el cliente que elijas, el proxy agent es dueño del pool de conexiones. Construir un HttpsProxyAgent o ProxyAgent fresco por cada petición tira el keep-alive y paga un handshake TCP + TLS completo a través del proxy cada vez, la sobrecarga que la guía de latencia existe para eliminar. Construye el agent una vez para una identidad dada y reutilízalo entre peticiones. Crea el cliente Axios/Got (o el dispatcher de undici) al arrancar y consérvalo.

Trampa 2: acota tu concurrencia, el event loop no lo hará por ti

El event loop de Node hace trivial disparar mil peticiones a la vez, y nada te detiene. await Promise.all(urls.map(fetchOne)) sobre un array grande abrirá cada conexión simultáneamente, lo que agota sockets en tu lado y parece un ataque para el destino. Limita las peticiones en vuelo con un pequeño limitador de concurrencia (p-limit es la elección común) o una cola simple:

import pLimit from 'p-limit';
const limit = pLimit(8); // como mucho 8 peticiones en vuelo
const results = await Promise.all(
urls.map(url => limit(() => client.get(url)))
);

Limita la concurrencia por host de destino, no solo de forma global, para que un sitio frágil no reciba una paliza mientras uno permisivo pasa hambre. Más paralelismo más allá de la tolerancia de un destino compra bloqueos, no throughput (cómo evitar que te bloqueen). Ajusta el límite a lo que cada host tolera.

Trampa 3: los errores async necesitan manejo explícito

Un fallo de proxy o de conexión aparece como una promesa rechazada, y un rechazo no manejado puede tumbar el proceso o, peor, dejar caer en silencio una tarea de un lote. Envuelve cada petición para que un fallo de transporte reintente con una identidad fresca en lugar de tirar la corrida:

async function fetchWithRetry(client, url, attempts = 3) {
for (let i = 0; i < attempts; i++) {
try {
return await client.get(url);
} catch (err) {
if (i === attempts - 1) throw err;
// transitorio (ECONNRESET, timeout, proxy 5xx): retrocede y reintenta
await new Promise(r => setTimeout(r, 500 * 2 ** i));
}
}
}

Distingue una conexión rota que vale la pena reintentar de una ralentización deliberada; un timeout es un intento roto, un 429 es el servidor pidiendo espacio y debería retroceder, no martillear.

Rotar geo y sesiones

Como el targeting vive en el nombre de usuario, una identidad distinta es una URL de proxy distinta, lo que significa un agent distinto. El patrón eficiente es cachear un agent por identidad para que mantengas el pool de conexiones por sesión en lugar de reconstruirlo:

const agents = new Map();
function agentFor(country, sid) {
const key = `${country}:${sid ?? 'rotate'}`;
if (!agents.has(key)) {
const u = `${process.env.SHIFTER_USER}-country-${country}` +
(sid ? `-sid-${sid}-ttl-600` : '');
const url = `http://${u}:${process.env.SHIFTER_PASS}@p.shifter.io:443`;
agents.set(key, new HttpsProxyAgent(url));
}
return agents.get(key);
}
// por petición:
await axios.get(targetUrl, { httpsAgent: agentFor('de', 'job-42'), proxy: false });

Dale a cada unidad lógica de trabajo su propio sid y rota entre unidades, no a mitad de flujo (sticky vs rotativo cubre la distinción), y mapea el trabajo a identidades como describe el post sobre reparto de carga.

Verifica que de verdad estás en el proxy

Antes de hacer benchmark o depurar cualquier otra cosa, confirma la IP de salida:

const res = await client.get('http://ip-api.com/json');
console.log(res.data); // espera una IP residencial en el país objetivo

Tu propia IP significa que el agent no se está aplicando (con Axios, casi siempre un proxy: false que falta). Un cuelgue significa que la salida local está bloqueada. Ambos se cubren en la guía de diagnóstico de timeouts.

Preguntas frecuentes

¿Por qué la opción proxy de Axios no funciona con mi proxy HTTPS? La opción proxy incorporada de Axios no tunela HTTPS con autenticación de forma fiable. Usa un httpsAgent construido con https-proxy-agent y pon proxy: false para que Axios deje de intentar manejarlo. Esa combinación es el camino fiable y arregla el síntoma de “devuelve mi IP real”.

¿Necesito https-proxy-agent si uso fetch nativo? No. El fetch de Node 18+ está respaldado por undici, que tiene su propio ProxyAgent que pasas como el dispatcher (o pones en global con setGlobalDispatcher). https-proxy-agent es para Axios, Got y los módulos incorporados http/https.

¿Por qué import got from 'got' lanza en mi proyecto? Got es ESM puro desde la v12, así que necesita un proyecto ESM ("type": "module") o un import() dinámico. En CommonJS, quédate en Got v11 o usa Axios/undici. Esto es un tema de sistema de módulos, no de proxy.

¿Cómo roto IPs por petición en Node.js? Varía el nombre de usuario del proxy, lo que significa una URL de proxy distinta y un agent distinto. Cachea un agent por identidad en un Map para que cada sesión mantenga su propio pool de conexiones, y elige el agent por petición. Omite el sid en el nombre de usuario para rotar en cada conexión nueva.

¿Axios, Got o fetch para scraping? Los tres funcionan. El fetch nativo + undici tiene cero dependencias extra en Node moderno; Got tiene reintentos y streams ergonómicos; Axios es ubicuo y familiar pero necesita el arreglo de proxy: false. Elige según ergonomía y dependencias existentes, la configuración del proxy y las trampas de arriba aplican a los tres.

En resumen

Node.js más proxies residenciales es rápido de montar en cuanto conoces la única regla no obvia: con Axios, usa un https-proxy-agent y pon proxy: false, nunca la opción proxy incorporada; con Got, pon el agent en agent.https; con fetch nativo, pasa un ProxyAgent de undici como el dispatcher. Luego reutiliza el agent para que las conexiones se mantengan calientes, acota tu concurrencia porque el event loop no lo hará, maneja los errores async para que una petición mala reintente en lugar de crashear, y varía el nombre de usuario del proxy para cambiar geo o sesión.

Hazlo bien y Node maneja la recolección concurrente tan bien como cualquier cosa. Apúntalo al gateway residencial, y recuerda que la calidad del pool decide con qué frecuencia reintentas siquiera (reputación de IP). La página de precios tiene los planes por GB para probarlo contra tus propios destinos.

¿Listo para empezar?

Prueba los proxies residenciales de Shifter, más de 205M IPs, más de 195 países, desde 0,75 $/GB.

Comenzar