Node.js
Proxy integration for undici, axios, got and native fetch, with connection pool control and rotation.
Node's built-in fetch and http modules do not read HTTP_PROXY and cannot be pointed at a proxy directly. You must supply an agent or dispatcher explicitly.
undici (recommended)
Node 18+ bundles undici as the fetch implementation, and its ProxyAgent is the cleanest option available today.
npm install undici
import { ProxyAgent, request } from 'undici';
const PROXY = 'http://USERNAME-country-US-session-job1-time-30:PASSWORD@GATEWAY_HOST:58971';
const dispatcher = new ProxyAgent(PROXY);
const { statusCode, body } = await request('https://api.ipify.org?format=json', {
dispatcher,
});
console.log(statusCode, await body.json());
await dispatcher.close();
With global fetch
import { ProxyAgent, setGlobalDispatcher } from 'undici';
setGlobalDispatcher(
new ProxyAgent('http://USERNAME-country-US:PASSWORD@GATEWAY_HOST:58971'),
);
// Every fetch from here on goes through the proxy
const response = await fetch('https://api.ipify.org');
console.log(await response.text());
Rotating IPs
Omit session, and use a fresh dispatcher each time:
import { ProxyAgent, request } from 'undici';
const PROXY = 'http://USERNAME-country-US:PASSWORD@GATEWAY_HOST:58971';
for (let i = 0; i < 5; i += 1) {
const dispatcher = new ProxyAgent(PROXY);
const { body } = await request('https://api.ipify.org', { dispatcher });
console.log(await body.text());
await dispatcher.close();
}
Concurrency with per-task sessions
import { ProxyAgent, request } from 'undici';
const GATEWAY = 'GATEWAY_HOST:58971';
const USER = 'USERNAME';
const PASSWORD = 'PASSWORD';
function proxyFor(sessionId, country = 'US', minutes = 30) {
const user = `${USER}-country-${country}-session-${sessionId}-time-${minutes}`;
return `http://${user}:${PASSWORD}@${GATEWAY}`;
}
async function fetchWith(sessionId) {
const dispatcher = new ProxyAgent(proxyFor(sessionId));
try {
const { body } = await request('https://api.ipify.org', { dispatcher });
return await body.text();
} finally {
await dispatcher.close();
}
}
const results = await Promise.all(
// session values allow only [A-Za-z0-9.-], so use dots rather than underscores
Array.from({ length: 8 }, (_, i) => fetchWith(`task.${i}`)),
);
console.log(results);
Controlling the connection pool
import { ProxyAgent } from 'undici';
const dispatcher = new ProxyAgent({
uri: 'http://USERNAME-country-US:PASSWORD@GATEWAY_HOST:58971',
connections: 16,
// The gateway's idle timeout is 5 minutes; going shorter avoids reusing a closed connection
keepAliveTimeout: 60_000,
keepAliveMaxTimeout: 240_000,
});
axios
axios handles its proxy option poorly for HTTPS requests; in practice use httpsAgent:
npm install axios https-proxy-agent
import axios from 'axios';
import { HttpsProxyAgent } from 'https-proxy-agent';
const PROXY = 'http://USERNAME-country-US-session-job1-time-30:PASSWORD@GATEWAY_HOST:58971';
const agent = new HttpsProxyAgent(PROXY);
const client = axios.create({
httpAgent: agent,
httpsAgent: agent,
// Required, otherwise axios adds its own proxy handling and conflicts with the agent
proxy: false,
timeout: 30_000,
});
const { data } = await client.get('https://api.ipify.org?format=json');
console.log(data);
got
npm install got hpagent
import got from 'got';
import { HttpProxyAgent, HttpsProxyAgent } from 'hpagent';
const PROXY = 'http://USERNAME-country-US:PASSWORD@GATEWAY_HOST:58971';
const client = got.extend({
agent: {
http: new HttpProxyAgent({ proxy: PROXY, keepAlive: true }),
https: new HttpsProxyAgent({ proxy: PROXY, keepAlive: true }),
},
timeout: { request: 30_000 },
retry: {
limit: 3,
// Distinguish proxy handshake failures from destination responses; retry only safe-to-replay requests
statusCodes: [429, 500, 502, 503, 504],
},
});
console.log(await client('https://api.ipify.org').text());
SOCKS5
npm install socks-proxy-agent
import { SocksProxyAgent } from 'socks-proxy-agent';
import { request } from 'undici';
// socks5h lets the proxy resolve the hostname
const agent = new SocksProxyAgent(
'socks5h://USERNAME-country-US:PASSWORD@GATEWAY_HOST:58971',
);
// With node:https
import https from 'node:https';
https.get('https://api.ipify.org', { agent }, (response) => {
response.setEncoding('utf8');
response.on('data', (chunk) => process.stdout.write(chunk));
});
Native http module with CONNECT
When you need full control over the protocol exchange (implementing your own fingerprint, for instance), send CONNECT by hand:
import http from 'node:http';
import tls from 'node:tls';
const USER = 'USERNAME-country-US';
const PASSWORD = 'PASSWORD';
const auth = Buffer.from(`${USER}:${PASSWORD}`).toString('base64');
const req = http.request({
host: 'GATEWAY_HOST',
port: 58971,
method: 'CONNECT',
path: 'api.ipify.org:443',
headers: { 'Proxy-Authorization': `Basic ${auth}` },
});
req.on('connect', (res, socket) => {
if (res.statusCode !== 200) {
console.error('tunnel failed:', res.statusCode);
socket.destroy();
return;
}
const tlsSocket = tls.connect({ socket, servername: 'api.ipify.org' }, () => {
tlsSocket.write('GET / HTTP/1.1\r\nHost: api.ipify.org\r\nConnection: close\r\n\r\n');
});
tlsSocket.pipe(process.stdout);
});
req.end();
Building a pool from the Extract API
const API = 'https://api.example.com/api/v1/proxy/extract';
async function fetchProxyPool(apiKey, count = 10) {
const params = new URLSearchParams({
apikey: apiKey,
num: String(count),
type: 'json',
country: 'US',
session: 'sticky',
time: '10',
});
const response = await fetch(`${API}?${params}`);
if (!response.ok) {
throw new Error(`extraction failed: ${response.status} ${await response.text()}`);
}
const { data } = await response.json();
// Passwordless ports, no credentials needed
return data.map((item) => `http://${item.ip}:${item.port}`);
}
const pool = await fetchProxyPool('YOUR_API_KEY', 5);
console.log(pool);
Troubleshooting checklist
| Symptom | Likely cause |
|---|---|
| Requests bypass the proxy entirely | Native fetch without a dispatcher; axios missing proxy: false |
407 | Wrong credentials; the - in the username mishandled |
400 | invalid_proxy_parameters: option syntax, order or dependency |
402 | traffic_exhausted / account_quota_exhausted / quota_exhausted: traffic or quota exhausted |
403 | account_disabled / account_expired / access_denied / target_denied: account or policy denial |
429 | connection_limit: reduce concurrency and connection rate |
502 | Exit connection failed |
503 | No eligible route or service temporarily unavailable |
504 | Proxy connection establishment timed out |
Frequent ECONNRESET | Pool keepAlive exceeds the gateway's 5-minute idle timeout |
| IP never changes | Dispatcher/agent reused, so the connection was never rebuilt |
For HTTP / CONNECT failures, inspect X-NextProxy-Error and JSON error.code. Handle destination statuses and CONNECT handshake exceptions separately. See the error reference for the full mapping.