A Node.js proxy for fetch takes two imports from the undici package: fetch and ProxyAgent. Pass the agent as dispatcher and every request tunnels through the proxy, with TLS running end to end to the site.

import { fetch, ProxyAgent } from 'undici';

const dispatcher = new ProxyAgent('http://USERNAME:PASSWORD@HOST:PORT');
const res = await fetch('https://api.ipify.org?format=json', { dispatcher });
console.log(await res.json());

The snippets on this page ran on 2026-09-29 on Node 22.23 with undici 8.11, axios 1.20, https-proxy-agent 9.1 and socks-proxy-agent 10.1, against a local authenticating HTTP proxy and a SOCKS5 proxy that enforces a username and password.

Before you start: copy your proxy details

  1. Open your order in the dashboard at https://app.proxyhive.io.
  2. Copy HOST, PORT, USERNAME and PASSWORD. Each ISP or datacenter IP is its own endpoint, with separate HTTP, HTTPS and SOCKS5 ports listed on the order. The USER:PASS@HOST:PORT copy format drops into a proxy URL after http://.
  3. Keep them in the environment:
export PROXY_URL="http://USERNAME:PASSWORD@HOST:PORT"
npm install undici axios https-proxy-agent socks-proxy-agent

For a first test, one static ISP proxy is enough.

Node fetch proxy with undici ProxyAgent

import { fetch, ProxyAgent } from 'undici';

const dispatcher = new ProxyAgent(process.env.PROXY_URL);

const res = await fetch('https://api.ipify.org?format=json', {
  dispatcher,
  signal: AbortSignal.timeout(30_000),
});
console.log(res.status, await res.json());

Import fetch from undici too. Passing an npm-installed ProxyAgent to Node's global fetch failed in our run with invalid onRequestStart method, because Node 22 bundles an older undici (6.x) whose dispatcher interface differs. The undici documentation shows the same pairing.

If the password has characters that break a URL, use the object form and build the header yourself:

const dispatcher = new ProxyAgent({
  uri: `http://${process.env.PROXY_HOST}:${process.env.PROXY_PORT}`,
  token: `Basic ${Buffer.from(`${process.env.PROXY_USER}:${process.env.PROXY_PASS}`).toString('base64')}`,
});

Environment variables: HTTPS_PROXY and NODE_USE_ENV_PROXY

For code you cannot edit, Node 22.21 and later can route the built-in fetch, http and https through the standard variables:

NODE_USE_ENV_PROXY=1 HTTPS_PROXY="$PROXY_URL" node app.mjs

It printed an "experimental" warning on 22.23 but worked. Inside your own code, undici's EnvHttpProxyAgent reads HTTP_PROXY, HTTPS_PROXY and NO_PROXY the same way.

Axios proxy with https-proxy-agent

import axios from 'axios';
import { HttpsProxyAgent } from 'https-proxy-agent';

const agent = new HttpsProxyAgent(process.env.PROXY_URL);
const { data } = await axios.get('https://api.ipify.org?format=json', {
  httpsAgent: agent,
  proxy: false,
  timeout: 30_000,
});
console.log(data);

proxy: false stops axios from applying its own proxy logic on top of the agent, including any HTTPS_PROXY in the environment.

Why not axios's built-in proxy option?

We captured what axios sends to a proxy for an HTTPS URL. Versions 1.7.9, 1.13.2 and 1.16.0 sent GET https://api.ipify.org/... HTTP/1.1 in plain text: no CONNECT tunnel, so the full URL and headers cross the wire to the proxy unencrypted, and many proxies reject it. From 1.17.0 axios opens a CONNECT tunnel itself, and on 1.20 the built-in option worked:

await axios.get('https://api.ipify.org?format=json', {
  proxy: {
    protocol: 'http',
    host: process.env.PROXY_HOST,
    port: Number(process.env.PROXY_PORT),
    auth: { username: process.env.PROXY_USER, password: process.env.PROXY_PASS },
  },
});

If you cannot guarantee the axios version everywhere your code runs, the agent is the safer pattern.

Proxy authentication: credentials or an IP allowlist

Credentials in the URL become a Proxy-Authorization: Basic header on the CONNECT request. The alternative is to switch the IP to allowlist authentication on the order in the dashboard and add your server's public IP; then the URL is just http://HOST:PORT. Allowlists suit servers with fixed addresses, not laptops.

SOCKS5 proxy in Node.js with socks-proxy-agent

Use the SOCKS5 port from the order:

import axios from 'axios';
import { SocksProxyAgent } from 'socks-proxy-agent';

const agent = new SocksProxyAgent('socks5h://USERNAME:PASSWORD@HOST:SOCKS5_PORT');
const { data } = await axios.get('https://api.ipify.org?format=json', {
  httpAgent: agent,
  httpsAgent: agent,
});

socks5h:// lets the proxy resolve the hostname; socks5:// resolves it locally and sends an IP. Our SOCKS server logged exactly that. See HTTP vs SOCKS5 proxies for when to pick SOCKS at all.

Verify the exit IP and rotate across static IPs

Create one ProxyAgent per IP and reuse them, so each keeps its connection pool warm:

import { fetch, ProxyAgent } from 'undici';

const agents = process.env.PROXY_URLS.split(',').map((url) => new ProxyAgent(url));
let next = 0;

for (let i = 0; i < 3; i++) {
  const dispatcher = agents[next++ % agents.length];
  const res = await fetch('https://api.ipify.org?format=json', {
    dispatcher,
    signal: AbortSignal.timeout(30_000),
  });
  console.log(await res.json());
}

Compare the printed IPs with the addresses on your order. The ideas carry over from rotating proxies in Python unchanged.

Timeouts and retries

fetch has no overall timeout, so pass AbortSignal.timeout(). For retries, wrap the proxy agent in undici's RetryAgent:

import { ProxyAgent, RetryAgent } from 'undici';

const dispatcher = new RetryAgent(new ProxyAgent(process.env.PROXY_URL), {
  maxRetries: 3,
  statusCodes: [429, 500, 502, 503, 504],
});

It backs off between attempts and throws once retries run out.

Common Node.js proxy errors

ErrorCauseFix
invalid onRequestStart methodGlobal fetch with an npm undici agentImport fetch from undici
TypeError: fetch failedAnything below the HTTP layer, including a rejected CONNECTLog err.cause; with a 407 ours read only Request was cancelled., so check credentials first
axios Request failed with status code 407Wrong username or passwordRe-copy from the order
Socks5 Authentication failedWrong SOCKS credentials, or the HTTP port used for SOCKSUse the SOCKS5 port and its credentials

For status codes coming from the target, see proxy error codes.

Next steps

  • Drive a browser from Node through the same IPs with Puppeteer or Playwright.
  • Check an endpoint from the shell first with cURL.
  • Connection reference: the docs.