Tutorials4 min readOct 11, 2026

Proxies in Node.js: fetch with undici, axios and SOCKS5

Node's built-in fetch has no proxy option, so here is what works instead. Authenticated proxies with undici, axios and SOCKS5, plus rotation, sticky sessions and concurrency.

By crawlproxies
Proxies in Node.js: fetch with undici, axios and SOCKS5

Node.js 18+ ships fetch, but it doesn't take a proxy setting. The fix is a dispatcher (for undici) or an agent (for axios and other HTTP clients). This guide shows both, with authenticated crawlproxies credentials, then covers rotation, sticky sessions and running many requests at once.

Get your username and password from the generator. The examples use the Residential gateway geo.crawlproxies.com (HTTP port 8080, SOCKS5 port 1080).

undici is the HTTP client that powers Node's own fetch. Install it and use its fetch together with a ProxyAgent:

bash
npm i undici
js
import { fetch, ProxyAgent } from "undici";

const agent = new ProxyAgent({
  uri: "http://geo.crawlproxies.com:8080",
  token: "Basic " + Buffer.from("USERNAME:PASSWORD").toString("base64"),
});

const res = await fetch("https://ipinfo.io/json", { dispatcher: agent });
console.log(res.status, await res.json());

The token becomes the Proxy-Authorization header. HTTPS sites are tunnelled through the proxy, so the connection to the website stays encrypted.

Tip: import fetch from undici rather than using the global one. The global fetch uses the copy of undici bundled with your Node version, and mixing it with a different installed version can fail in confusing ways.

To send every undici request through the proxy without passing dispatcher each time:

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

setGlobalDispatcher(new ProxyAgent({
  uri: "http://geo.crawlproxies.com:8080",
  token: "Basic " + Buffer.from("USERNAME:PASSWORD").toString("base64"),
}));

const res = await fetch("https://ipinfo.io/ip");

Option 2: axios with a proxy agent

axios has a built-in proxy option, but for HTTPS sites an agent is more reliable. Install https-proxy-agent:

bash
npm i axios https-proxy-agent
js
import axios from "axios";
import { HttpsProxyAgent } from "https-proxy-agent";

const agent = new HttpsProxyAgent("http://USERNAME:PASSWORD@geo.crawlproxies.com:8080");

const { data } = await axios.get("https://ipinfo.io/json", {
  httpsAgent: agent,
  proxy: false,          // turn off axios's own proxy handling so the agent is used
  timeout: 30_000,
});
console.log(data.ip, data.country);

For plain http:// sites, add http-proxy-agent and pass its HttpProxyAgent as httpAgent the same way.

Option 3: SOCKS5

bash
npm i socks-proxy-agent
js
import axios from "axios";
import { SocksProxyAgent } from "socks-proxy-agent";

const agent = new SocksProxyAgent("socks5h://USERNAME:PASSWORD@geo.crawlproxies.com:1080");

const { data } = await axios.get("https://ipinfo.io/json", {
  httpAgent: agent,
  httpsAgent: agent,
  proxy: false,
});

socks5h resolves hostnames through the proxy as well, so DNS lookups don't come from your machine.

Geo-targeting

Location is part of the username, so it's a string change:

js
const username = (opts = {}) => {
  let u = "USERNAME";
  if (opts.country) u += `-country-${opts.country}`;
  if (opts.country && opts.city) u += `-city-${opts.city}`;
  return u;
};

const agent = new ProxyAgent({
  uri: "http://geo.crawlproxies.com:8080",
  token: "Basic " + Buffer.from(`${username({ country: "gb", city: "london" })}:PASSWORD`).toString("base64"),
});

Country codes are two letters; cities and US states are lowercase with no spaces. The geo-targeting guide lists the options.

Rotation and sticky sessions

Without a session in the username, every new connection gets a new IP. Agents and dispatchers keep connections open and reuse them, so a burst of requests through one agent can share an exit IP. When you need a guaranteed fresh IP, use a fresh agent and close it when you're done:

js
const TOKEN = "Basic " + Buffer.from("USERNAME:PASSWORD").toString("base64");

async function freshIp() {
  const agent = new ProxyAgent({ uri: "http://geo.crawlproxies.com:8080", token: TOKEN });
  try {
    const res = await fetch("https://ipinfo.io/ip", { dispatcher: agent });
    return (await res.text()).trim();
  } finally {
    await agent.close();
  }
}

For the opposite (the same IP across a login or checkout), add a session id and a duration in seconds to the username:

js
import { randomUUID } from "node:crypto";

const sessionUser = `USERNAME-country-us-session-${randomUUID().slice(0, 8)}-time-1800`;

Keep one session id per account or task. Sticky vs rotating sessions explains the trade-offs.

Many requests at once

Promise.all on hundreds of URLs opens hundreds of connections at once. A small pool keeps it under control:

js
async function mapLimit(items, limit, fn) {
  const results = new Array(items.length);
  let next = 0;
  const worker = async () => {
    while (next < items.length) {
      const i = next++;
      try { results[i] = await fn(items[i]); }
      catch (err) { results[i] = { error: err.message }; }
    }
  };
  await Promise.all(Array.from({ length: limit }, worker));
  return results;
}

const urls = Array.from({ length: 50 }, (_, i) => `https://example.com/page/${i + 1}`);
const results = await mapLimit(urls, 10, async (url) => {
  const res = await fetch(url, { dispatcher: agent, signal: AbortSignal.timeout(30_000) });
  return { url, status: res.status };
});

Start around 10 concurrent requests and raise it slowly while watching your error rate.

Errors you might see

ErrorUsually means
Proxy response (407) !== 200 when HTTP Tunneling (undici)Wrong username or password
Request failed with status code 407 (axios)The same: check the credentials and targeting syntax
ECONNREFUSEDWrong host or port, or HTTP port used with a SOCKS5 client
TimeoutError / ETIMEDOUTA slow exit IP: retry, and keep timeouts on every request
403 / 429 from the siteBlocking or rate limiting: see the debugging checklist

Driving a real browser instead? See Playwright and Puppeteer with authenticated proxies.

Written by
crawlproxies
Create account