WeProxy

Guide

How to Use a Proxy in Node.js: axios, undici and fetch Examples

12 min read

In Node.js, a proxy is set with NODE_USE_ENV_PROXY for the built-in fetch, with ProxyAgent in undici and with the proxy setting in axios. HTTP and SOCKS5 examples, sticky session handling, checking your exit IP, and proxy errors by client.

Key takeaways

  • Node.js doesn’t read proxy settings on its own; the built-in fetch uses the HTTP_PROXY, HTTPS_PROXY and NO_PROXY variables only when started with NODE_USE_ENV_PROXY=1 or --use-env-proxy (Node.js 24 and versions after 22.21).
  • In undici, the proxy is passed to the request as dispatcher: new ProxyAgent("http://USER:PASSWORD@host:port"); fetch must be imported from the same undici package, otherwise a version mismatch can make the request fail.
  • axios takes the proxy through the proxy: { protocol, host, port, auth } setting, and the password goes into the auth object without encoding. When you use https-proxy-agent, add proxy: false so it doesn’t conflict with environment variables.
  • For SOCKS5, use socks-proxy-agent with axios and node:https, or the experimental Socks5ProxyAgent in undici; the socks5h scheme leaves DNS resolution to the proxy.
  • undici carries the real cause of an error in err.cause; when you get a 407, don’t retry, but check that the password is encoded with encodeURIComponent and that your plan is active.

There are three common ways to use a proxy in Node.js: start Node with NODE_USE_ENV_PROXY=1 and set the HTTPS_PROXY variable for the built-in fetch, pass the undici package’s ProxyAgent class to the request as its dispatcher, or use the proxy setting in axios. In every case the proxy address takes the form http://USER:PASSWORD@host:port; for SOCKS5 you need socks-proxy-agent or undici’s Socks5ProxyAgent class.

Which one you pick depends on the HTTP client you use. Node.js doesn’t read proxy settings for http.request and fetch on its own, so you either turn on environment variable support or configure the proxy separately for each client. The examples use the WeProxy gateway address gw.weproxy.com.tr:8989; USER and PASSWORD are the credentials from your dashboard.

How do you write a proxy address in Node.js?

Every client accepts the proxy in the same URL format:

scheme://username:password@host:port

The scheme is the protocol of the proxy server. Even when the target site is HTTPS, an HTTP proxy address starts with http://: the client sends a CONNECT request to the proxy, and the TLS connection is set up inside that tunnel. If you write the address with https://, the client tries to speak TLS with the proxy itself, and undici fails with ERR_SSL_WRONG_VERSION_NUMBER. The HTTP vs SOCKS5 article explains how an HTTP proxy differs from SOCKS5.

If the password contains @, :, / or #, encode it with encodeURIComponent before putting it in the URL. An unencoded @ breaks address parsing, and undici throws TypeError: Invalid URL:

const user = encodeURIComponent(process.env.WEPROXY_USERNAME);
const pass = encodeURIComponent(process.env.WEPROXY_PASSWORD);
export const PROXY = `http://${user}:${pass}@gw.weproxy.com.tr:8989`;

Keep credentials in environment variables or a secret manager so the password doesn’t end up in Git along with your code. If the dashboard gave you a list in ip:port:user:pass format, the proxy format converter turns those lines into URLs. IPv6 addresses, socks5h and similar details are in the proxy formats guide.

How do you use a proxy with the built-in fetch in Node.js?

In Node.js 24 and in versions after 22.21, the built-in fetch can read the HTTP_PROXY, HTTPS_PROXY and NO_PROXY variables. This behavior is off by default; you turn it on with the NODE_USE_ENV_PROXY=1 variable or the --use-env-proxy flag. Without it, Node ignores the variables and the request goes out directly from your own IP.

export HTTPS_PROXY="http://USER:[email protected]:8989"
export HTTP_PROXY="$HTTPS_PROXY"
export NO_PROXY="localhost,127.0.0.1"
NODE_USE_ENV_PROXY=1 node app.mjs

The same setup in PowerShell:

$env:HTTPS_PROXY = "http://USER:[email protected]:8989"
$env:HTTP_PROXY = $env:HTTPS_PROXY
node --use-env-proxy app.mjs

app.mjs contains no proxy code at all:

const res = await fetch('https://api.ipify.org?format=json', {
  signal: AbortSignal.timeout(30_000),
});
console.log(await res.json()); // { ip: '<proxy exit IP>' }

In current versions the flag also routes the global agents of the node:http and node:https modules through the proxy, so older-style calls such as https.get go out through the proxy without any code changes. Details are in the Node.js CLI documentation. Use it to put an existing application behind a proxy without touching its code. The whole process uses a single proxy, so if you need a different proxy per request, use the next method.

How do you use a proxy with undici ProxyAgent?

undici is the HTTP client underneath Node.js’s fetch implementation, and it’s also published as a separate package on npm. Its ProxyAgent class sets the proxy at the request level:

npm install undici
import { ProxyAgent, fetch } from 'undici';
import { PROXY } from './proxy.mjs';

const dispatcher = new ProxyAgent(PROXY);

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

await dispatcher.close();

Import fetch from the same package as ProxyAgent. The undici version bundled with Node can lag behind the one on npm: if you pass a ProxyAgent from npm’s undici 8 to Node.js 24’s built-in fetch, the request fails with fetch failed, and the underlying cause is invalid onRequestStart method.

If you’d rather not embed the password in the URL, you can pass the credentials as a header value. This form doesn’t need percent-encoding:

const basic = Buffer.from(
  `${process.env.WEPROXY_USERNAME}:${process.env.WEPROXY_PASSWORD}`
).toString('base64');

const dispatcher = new ProxyAgent({
  uri: 'http://gw.weproxy.com.tr:8989',
  token: `Basic ${basic}`,
});

To send every fetch call in the application through the proxy, use setGlobalDispatcher(new ProxyAgent(PROXY)). There’s also an option that reads environment variables without needing a process flag: EnvHttpProxyAgent reads HTTPS_PROXY and NO_PROXY itself. The full list of options is in the undici ProxyAgent documentation.

How do you use a proxy with axios?

In Node.js, axios takes the proxy through its proxy setting. Credentials go in separate fields, so you don’t have to encode special characters:

import axios from 'axios';

const client = axios.create({
  proxy: {
    protocol: 'http',
    host: 'gw.weproxy.com.tr',
    port: 8989,
    auth: {
      username: process.env.WEPROXY_USERNAME,
      password: process.env.WEPROXY_PASSWORD,
    },
  },
  timeout: 30_000,
});

const { data } = await client.get('https://api.ipify.org?format=json');
console.log(data);

Current axios versions open a CONNECT tunnel through the proxy for HTTPS targets. Version 1.16.1 fixed a bug where, in some configurations, the contents of an HTTPS request could reach the HTTP proxy unencrypted (axios changelog). If you’re on an older version, update axios or hand the tunnel over to the https-proxy-agent package:

import axios from 'axios';
import { HttpsProxyAgent } from 'https-proxy-agent';
import { PROXY } from './proxy.mjs';

const client = axios.create({
  httpsAgent: new HttpsProxyAgent(PROXY),
  proxy: false,
});

By default, axios reads the HTTPS_PROXY and NO_PROXY variables; without the proxy: false line, a proxy from the environment can conflict with your agent. And if HTTPS_PROXY holds a percent-encoded password, axios sends it to the proxy still encoded, which ends in a 407. When the password has special characters, use the auth object in axios.

How do you use a SOCKS5 proxy in Node.js?

Neither the built-in fetch nor axios speaks SOCKS5 on its own. For axios and node:https, you use the socks-proxy-agent package:

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

const user = encodeURIComponent(process.env.WEPROXY_USERNAME);
const pass = encodeURIComponent(process.env.WEPROXY_PASSWORD);
const agent = new SocksProxyAgent(`socks5h://${user}:${pass}@gw.weproxy.com.tr:8989`);

const client = axios.create({ httpAgent: agent, httpsAgent: agent, proxy: false });
console.log((await client.get('https://api.ipify.org')).data);

The socks5h scheme leaves domain resolution to the proxy; with socks5, the target domain is resolved by your own DNS server. If you use undici, the Socks5ProxyAgent class does the same job and always resolves the domain on the proxy side. The class is still marked experimental and prints an ExperimentalWarning the first time you use it:

import { Socks5ProxyAgent, fetch } from 'undici';

const dispatcher = new Socks5ProxyAgent(`socks5://${user}:${pass}@gw.weproxy.com.tr:8989`);
const res = await fetch('https://api.ipify.org', { dispatcher });
console.log(await res.text());

SOCKS5 support depends on the plan. Rotating Residential IPv4 offers both HTTP and SOCKS5, while the real-SIM Mobile 5G Proxy works over HTTP only. These clients can’t carry UDP traffic such as games or VoIP; setups that need UDP are covered in the UDP proxy guide.

axios vs undici vs fetch

FeatureBuilt-in fetchundiciaxios
InstallationNone (Node.js 18+)npm install undicinpm install axios
How the proxy is setNODE_USE_ENV_PROXY=1 + HTTPS_PROXYdispatcher: new ProxyAgent(...)proxy: {...} or httpsAgent
Different proxy per requestNo, process-wideYesYes
CredentialsIn the URLIn the URL or tokenauth object
SOCKS5NoSocks5ProxyAgent (experimental)socks-proxy-agent
Environment variablesOnly with the flagWith EnvHttpProxyAgentOn by default
Best suited forAdding a proxy without touching codeNew code, multiple proxies, high concurrencyExisting axios projects

In a new project, undici gives you per-request proxies, SOCKS5 and connection pool settings in a single package. If your codebase is built on axios, there’s no need to switch clients; the proxy setting or https-proxy-agent is enough.

How do you manage rotating and sticky IPs in Node.js?

On a rotating gateway, the proxy address stays the same while the exit IP changes from the pool. On the Node.js side, what you need to watch is connection reuse. undici’s ProxyAgent reuses the tunnel it opened for each target on later requests, and since Node.js 19 keepAlive has been on by default in the http and https global agents. If IP rotation is applied per new connection, consecutive requests from the same agent can leave from the same IP. To see whether the gateway rotates per request or per connection, query api.ipify.org twice with the same agent, then twice with two separate agents.

Logins, shopping carts and multi-step forms need the opposite: the same IP across requests. In WeProxy residential and mobile pools, you get this by adding a session ID to the username. The username format and session duration vary by product, so take both from the dashboard. In code, opening a separate agent for each account is enough:

import { ProxyAgent, fetch } from 'undici';

// Usernames are copied from the dashboard; each profile carries its own session ID.
const PROFILES = {
  'account-1': 'USER_WITH_SESSION_1',
  'account-2': 'USER_WITH_SESSION_2',
};
const pass = encodeURIComponent(process.env.WEPROXY_PASSWORD);

const agents = Object.fromEntries(
  Object.entries(PROFILES).map(([name, user]) => [
    name,
    new ProxyAgent(`http://${encodeURIComponent(user)}:${pass}@gw.weproxy.com.tr:8989`),
  ])
);

for (const [name, dispatcher] of Object.entries(agents)) {
  const res = await fetch('https://api.ipify.org', { dispatcher });
  console.log(name, await res.text());
}

Two profiles shouldn’t share the same session ID; if they do, the target site sees two separate users on the same IP. The sticky vs rotating guide has a scenario table showing which jobs need sticky and which need rotating. The plan that offers both session types is on the rotating residential proxy page.

If you’re going to crawl a lot of URLs, cap concurrency instead of starting them all at once with Promise.all. Here’s a small pool that needs no extra packages:

async function runPool(items, limit, worker) {
  const results = [];
  let next = 0;
  const lanes = Array.from({ length: limit }, async () => {
    while (next < items.length) {
      const i = next++;
      results[i] = await worker(items[i]).catch((err) => err);
    }
  });
  await Promise.all(lanes);
  return results;
}

const urls = Array(20).fill('https://api.ipify.org');
const ips = await runPool(urls, 5, async (url) =>
  (await fetch(url, { dispatcher: agents['account-1'] })).text()
);

The limit is set by the target site: a sudden burst of requests from the same IP runs into the target’s rate limit and gets a 429.

How do you verify the proxy is working?

The quickest test is to check your exit IP with and without the proxy and compare the two:

import { ProxyAgent, fetch as undiciFetch } from 'undici';
import { PROXY } from './proxy.mjs';

const dispatcher = new ProxyAgent(PROXY);
const direct = await (await fetch('https://api.ipify.org')).text();
const viaProxy = await (await undiciFetch('https://api.ipify.org', { dispatcher })).text();

console.log({ direct, viaProxy });
if (direct === viaProxy) throw new Error('Request is not going through the proxy');

If the two values match, the proxy setting isn’t reaching the request. With the built-in fetch, check the NODE_USE_ENV_PROXY flag; in axios, check for a conflict with environment variables (proxy: false); and in every case, check the NO_PROXY list. The My IP tool shows the proxy IP’s country and network. To test the proxy independently of your code, use the proxy checker; a proxy that works there but not from Node points to a problem in the client settings. To run the same test from the terminal, use the commands in the using a proxy with cURL article.

Node.js proxy errors and how to fix them

The same problem shows up as a different error in each client. The results in the table were captured against a local test proxy with Node.js 24, undici 8, axios 1.21 and https-proxy-agent 9:

Situationundici fetchaxiosnode:https + HttpsProxyAgent
Credentials rejected (407)fetch failed; underlying cause “Proxy response (407) !== 200 when HTTP Tunneling”AxiosError, status: 407No error; the response’s statusCode is 407
Couldn’t connect to the proxyfetch failed; cause ECONNREFUSEDECONNREFUSEDerror event, ECONNREFUSED
Timed outTimeoutError with AbortSignal.timeoutECONNABORTEDCaught with req.setTimeout
Proxy address written with https://ERR_SSL_WRONG_VERSION_NUMBEREPROTO (“wrong version number”)EPROTO (“wrong version number”)
Password not encodedTypeError: Invalid URLNo problem with the auth objectTypeError: Invalid URL

undici keeps the real cause of the error in err.cause; if you only print err.message, every error looks like “fetch failed”. Log err.cause?.code and err.cause?.message as well.

When you get a 407, first check that the password is encoded, then that your plan is active. If you work with an IP whitelist, the exit IP of the server running the code must be registered in the dashboard; if a script that ran locally starts getting 407 after moving to a server, that’s the reason. Which products support which method is in the proxy authentication guide. Don’t retry on 407; with the same credentials the result won’t change.

429, 502, 503 and 504 can be temporary and call for retries with increasing intervals. Which layer each code comes from is explained in the proxy error codes guide. If the connection keeps timing out, your corporate network or cloud provider may be blocking outbound traffic on port 8989. If you get a certificate error, don’t turn off verification with NODE_TLS_REJECT_UNAUTHORIZED=0; that setting disables TLS verification for the entire process.

Compliance and security when using a proxy with Node.js

If you’re writing a script that collects data, follow the target’s robots.txt file (RFC 9309) and terms of use even though the requests go out through a proxy, and keep the request rate at a level the site can handle. If you process personal data, obligations under KVKK (Turkey) and GDPR apply regardless of the proxy. Permitted and prohibited uses of WeProxy services are listed on the acceptable use page.

Because the proxy URL contains the password, it can easily leak into error messages and logs. Before logging it, parse the address with new URL(PROXY) and clear the password field. Setup steps are on the Node.js integration page, and a runnable sample project is in the weproxy-io/nodejs-proxy repository. For browser automation, see the Puppeteer integration; to do the same things in Python, see how to use a proxy in Python. IP type and session choices for scraping jobs are covered on the web scraping page.

Frequently asked questions

How do I use a proxy with fetch in Node.js?

In Node.js 24 and versions after 22.21, set the HTTPS_PROXY variable to http://USER:PASSWORD@host:port and start the app with NODE_USE_ENV_PROXY=1 or node --use-env-proxy; built-in fetch requests then go out through the proxy. If you need a different proxy per request, install the undici package from npm, pass a ProxyAgent through the dispatcher option, and import fetch from undici as well.

Why does Node.js still use my real IP after I set HTTPS_PROXY?

By default, Node.js doesn’t read proxies from environment variables. The built-in fetch and node:https need NODE_USE_ENV_PROXY=1 or --use-env-proxy, and older versions don’t have this support at all. The NO_PROXY list may also cover the target domain. Query api.ipify.org with and without the proxy and compare the results to find the problem quickly.

How do I set up a proxy with a username and password in axios?

Inside axios.create, give the proxy setting protocol: "http", host, port and auth: { username, password }. The password goes into the auth object as is, with no percent-encoding needed. With older axios versions, passing the https-proxy-agent package as httpsAgent and adding proxy: false is a safer route for HTTPS requests; keeping axios up to date is also recommended.

How do I use a SOCKS5 proxy in Node.js?

The built-in fetch and axios don’t support SOCKS5 on their own. For axios or node:https, install the socks-proxy-agent package, create an agent with a socks5h://USER:PASSWORD@host:port address, and pass it to the httpAgent and httpsAgent fields. If you use undici, the experimental Socks5ProxyAgent class does the same job. SOCKS5 support depends on the plan; Mobile 5G Proxy works over HTTP only.

Why does undici only show a “fetch failed” error?

undici’s fetch throws network errors as a generic TypeError and carries the real cause in the err.cause field. For a 407, for example, the end of the chain holds the message “Proxy response (407) !== 200 when HTTP Tunneling”, and when the proxy can’t be reached you’ll find the ECONNREFUSED code. Log err.cause?.code and err.cause?.message too, sort errors by type, and retry only temporary ones.

Get Started

Test Your Proxy Setup Today

Check connectivity with free tools or talk to sales to pick the right package.