Documentation Python quickstart Blog Free tools Enterprise solutions hello@quanticdata.ioLog in

Node fetch proxy agent: node-fetch, built-in fetch and NODE_USE_ENV_PROXY

Three ways to send Node.js fetch through a proxy: on the left node-fetch with an https-proxy-agent passed as agent and built-in fetch with an undici ProxyAgent passed as dispatcher; in the middle the native-fetch code with the undici major that matches Node 24; the card lists the knob per method, including NODE_USE_ENV_PROXY=1 on Node 24 and 22.21 or later.
Three ways to send Node.js fetch through a proxy: on the left node-fetch with an https-proxy-agent passed as agent and built-in fetch with an undici ProxyAgent passed as dispatcher; in the middle the native-fetch code with the undici major that matches Node 24; the card lists the knob per method, including NODE_USE_ENV_PROXY=1 on Node 24 and 22.21 or later.

To send Node.js fetch requests through a proxy, attach an agent. node-fetch takes an HttpsProxyAgent in its agent option. Node's built-in fetch takes an undici ProxyAgent as dispatcher, installed at the same major version as Node's own undici. Node 24 and 22.21 or later can instead read HTTPS_PROXY when NODE_USE_ENV_PROXY=1 is set.

Every example on this page was run on 24 October 2026 with Node.js 24.21.0 through a QuanticData Residential Premium line for Chile, and the outputs shown are the real ones, with the exit IP masked. The version numbers come from the Node.js and undici documentation and from the npm registry, so you can check which method your Node version supports before you write a line. For a wider overview that also covers axios, see proxies in Node.js with fetch and axios.

Which method for which Node version

MethodWhat you installWhere the proxy goesNode versions
node-fetch + https-proxy-agentnode-fetch 3, https-proxy-agent 9agent optionhttps-proxy-agent 9 needs Node 20 or later
Built-in fetch + undici ProxyAgentundici at Node's own major: 6 on Node 22, 7 on Node 24, 8 on Node 26dispatcher optionfetch is built in since 18.0.0, stable since 21.0.0
NODE_USE_ENV_PROXY=1nothingHTTPS_PROXY, NO_PROXY24.0.0 and later, 22.21.0 and later
node --use-env-proxynothingthe same variables24.5.0 and later, 22.21.0 and later
http.setGlobalProxyFromEnv()nothingone call at startup25.4.0 and later, 24.14.0 and later
SOCKS5: socks-proxy-agent, or undici's SOCKS5 supportsocks-proxy-agent 10, or undici 7.23 or lateragent or dispatchersocks-proxy-agent 10 needs Node 20 or later

The built-in route through environment variables only takes HTTP or HTTPS proxy URLs, so SOCKS5 always goes through an agent. On a Node version older than the ones in the table, the variables are ignored without a warning: we ran the same command on Node 22.6.0 and the request left from our own IP.

Before you start: the proxy URL

All the methods below take the proxy as one URL: http://username:password@host:port. In the QuanticData dashboard open My proxies, then your plan; in the Endpoint generator choose the Country, set Session to Sticky, choose a Session TTL, keep the Protocol on HTTP and set Output format to http://user:pass@host:port. The Preview is the URL to paste.

The QuanticData Endpoint generator for a Residential Premium plan: Chile selected, Session set to Sticky with a 60-minute TTL, HTTP protocol, output format http://user:pass@host:port and the preview URL on residentialpremium.quantumproxies.io:8080, with the username and password blurred
The URL comes ready for Node: country, sticky session, protocol and the http://user:pass@host:port format.
  • Host and port: residentialpremium.quantumproxies.io, port 8080 for HTTP and 1080 for SOCKS5.
  • The username carries the targeting: USER-country-cl-sid-nf01-ttl-60m exits in Chile (-country-cl), keeps one IP for the session named nf01 (-sid-nf01) for 60 minutes (-ttl-60m). Copy it whole.
  • Special characters: if the password holds @, :, / or #, wrap it in encodeURIComponent() before it goes into the URL. undici decodes it again before it sends the credentials to the proxy.

In real code keep the username and password in environment variables or a secrets store; the examples write them inline as USER and PASS so the whole line is visible.

node-fetch with https-proxy-agent

node-fetch has no proxy setting of its own. It takes an agent, and https-proxy-agent is an agent that opens a CONNECT tunnel through an HTTP or HTTPS proxy. node-fetch 3 is an ES module: use import, or stay on node-fetch 2 if the project still loads modules with require().

npm install node-fetch https-proxy-agent
import fetch from 'node-fetch';
import { HttpsProxyAgent } from 'https-proxy-agent';

const proxy =
  'http://USER-country-cl-sid-nf01-ttl-60m:PASS@residentialpremium.quantumproxies.io:8080';
const agent = new HttpsProxyAgent(proxy);

const res = await fetch('https://ipinfo.io/json', { agent });
const { ip, city, region, country, timezone } = await res.json();
console.log(res.status, { ip, city, region, country, timezone });
200 {
  ip: '186.189.•••.•••',
  city: 'Santiago',
  region: 'Santiago Metropolitan',
  country: 'CL',
  timezone: 'America/Santiago'
}
VS Code with node-fetch.mjs, node-fetch and HttpsProxyAgent with the QuanticData URL for Chile passed as agent; in the terminal npm install node-fetch https-proxy-agent and the real run answering 200 from Santiago, Santiago Metropolitan, CL, America/Santiago, IP masked; five numbered steps beside it
node-fetch with https-proxy-agent, redrawn in VS Code with the real code and the real output of 24 October 2026.

Create the agent once and pass the same one to every request that should use that line; https-proxy-agent takes the usual http.Agent options as a second argument, such as { keepAlive: true } to reuse connections. The agent option also accepts a function that receives the URL and returns an agent, which is how one script can send https:// and http:// targets through different agents.

Node's built-in fetch with undici's ProxyAgent

Node's own fetch has no agent option. It runs on undici, and the Node.js documentation says a custom dispatcher can be passed in the options object; undici's ProxyAgent is that dispatcher. The class comes from the undici package on npm, and here is the step most guides skip: install the undici major that Node itself runs, or the two do not fit together.

node -p process.versions.undici
7.29.1

npm install undici@7
import { ProxyAgent } from 'undici';

const proxy =
  'http://USER-country-cl-sid-nf02-ttl-60m:[email protected]:8080';
const dispatcher = new ProxyAgent(proxy);

// fetch is Node's built-in global: no import
const res = await fetch('https://ipinfo.io/json', { dispatcher });
const { ip, city, region, country, timezone } = await res.json();
console.log(res.status, { ip, city, region, country, timezone });
VS Code with native-fetch.mjs, an undici ProxyAgent passed as dispatcher to Node's built-in fetch; in the terminal node -p process.versions.undici answering 7.29.1, npm install undici@7 and the real run through a QuanticData Chile line answering 200 from Santiago, IP masked; five numbered steps beside it
Built-in fetch with undici's ProxyAgent on Node 24.21.0: undici 7 to match the undici inside Node.
Node lineundici inside Node (latest release, October 2026)Install
Node 226.29.1 in 22.23.4npm install undici@6
Node 247.30.1 in 24.22.1npm install undici@7
Node 268.12.2 in 26.12.1npm install undici@8

The same file ran on Node 22.6.0 with undici 6 and gave the same Chile exit. Two other ways avoid the version question entirely: import fetch from undici together with ProxyAgent (import { fetch, ProxyAgent } from 'undici'), so both come from the same package, or make the proxy the default for every request with setGlobalDispatcher(dispatcher), which the Node.js documentation says affects both undici and Node's fetch.

No code changes: NODE_USE_ENV_PROXY and --use-env-proxy

Since Node 24.0.0 and 22.21.0, Node can read the proxy from the environment by itself. Set HTTPS_PROXY (and HTTP_PROXY for plain http:// targets), list the hosts that must skip the proxy in NO_PROXY, and turn the feature on with NODE_USE_ENV_PROXY=1 or, from 24.5.0 and 22.21.0, the --use-env-proxy flag. The Node.js documentation marks it Stability 1.1, active development.

export HTTPS_PROXY='http://USER-country-cl-sid-nf04-ttl-60m:PASS@residentialpremium.quantumproxies.io:8080'
export NO_PROXY='localhost,127.0.0.1'
NODE_USE_ENV_PROXY=1 node env-proxy.mjs
VS Code with env-proxy.mjs, a plain fetch with no agent; in the terminal node -v v24.21.0, export HTTPS_PROXY with the QuanticData URL, export NO_PROXY=localhost,127.0.0.1 and NODE_USE_ENV_PROXY=1 node env-proxy.mjs answering 200 from Santiago, CL, IP masked; four numbered steps beside it
A plain fetch() routed by the environment on Node 24.21.0. Redrawn screen with the real output.
  • It covers more than fetch. From 24.5.0 and 22.21.0 the http and https global agents use the same variables, so node-fetch with no agent at all also went out through the proxy in our run.
  • NO_PROXY formats: *, an exact host (example.com), a suffix (.example.com or *.example.com), an IP, an IP range (192.168.1.1-192.168.1.100) or a host with a port, separated by commas. If both forms are set, the lowercase variable wins.
  • From code: http.setGlobalProxyFromEnv() (25.4.0 and 24.14.0) turns the same thing on at runtime, for fetch and for http.request, and returns a function that restores the previous agents.
  • On older Node versions: undici's EnvHttpProxyAgent reads the same variables; pass it as the dispatcher or install it with setGlobalDispatcher(), from the undici major that matches Node, as above.

SOCKS5 in Node.js fetch

The environment route and https-proxy-agent speak HTTP to the proxy. For SOCKS5, use port 1080 with socks-proxy-agent in node-fetch. Write the URL as socks5h://: with socks5h the proxy resolves the host name, with socks5 your machine does, which can make a site in Chile resolve to a server near you.

import fetch from 'node-fetch';
import { SocksProxyAgent } from 'socks-proxy-agent';

// socks5h: the proxy resolves the host name, not your machine
const proxy = 'socks5h://USER-country-cl-sid-nf03-ttl-60m:PASS@residentialpremium.quantumproxies.io:1080';
const agent = new SocksProxyAgent(proxy);

const res = await fetch('https://ipinfo.io/json', { agent });
const { ip, city, region, country, timezone } = await res.json();
console.log(res.status, { ip, city, region, country, timezone });
200 {
  ip: '179.60.•••.•••',
  city: 'Santiago',
  region: 'Santiago Metropolitan',
  country: 'CL',
  timezone: 'America/Santiago'
}

With undici, recent releases accept a socks5:// URL in ProxyAgent and hand it to Socks5ProxyAgent, added in undici 7.23.0 and still experimental. It lets the proxy resolve the host name, and Node prints a one-time warning that is not an error. Our run with undici 8.12.3 answered from Chépica, O'Higgins Region:

(node:48991) ExperimentalWarning: SOCKS5 proxy support is experimental and subject to change

More on when SOCKS5 is worth it is on the page for SOCKS5 proxies.

One sticky IP per worker

The IP is chosen by the session ID in the username, not by the agent object. The same -sid- keeps the same exit for as long as its TTL lasts, up to 1440 minutes; a new -sid- is a new IP. This script opens a fresh connection for each request:

import { ProxyAgent } from 'undici';

const gateway = 'residentialpremium.quantumproxies.io:8080';
const line = (sid) => `http://USER-country-cl-sid-${sid}-ttl-60m:PASS@${gateway}`;

let last;
for (const sid of ['w01', 'w01', 'w02']) {
  const dispatcher = new ProxyAgent(line(sid)); // a new connection each time
  const res = await fetch('https://ipinfo.io/json', { dispatcher });
  const { ip, city } = await res.json();
  console.log(sid, ip, city, ip === last ? '(same IP)' : '(new IP)');
  last = ip;
  await dispatcher.close();
}
w01 179.57.•••.••• Puerto Montt (new IP)
w01 179.57.•••.••• Puerto Montt (same IP)
w02 181.42.•••.••• Santiago (new IP)

For a pool of workers, give each one its own session ID and one agent for its whole life. The generator makes the lines for you: with Session set to Sticky, Generate makes as many URLs as the Quantity field says, ten by default, each with its own session, and Copy all or Download takes them out in the same format.

The Generate 10 sessions button in the QuanticData Endpoint generator with Copy all and Download, and the list of sticky URLs ready to copy, credentials blurred
Ten sticky URLs, one per worker. Credentials blurred.

Residential Premium, ISP and mobile proxies in Node

Proxy typeURL in NodeGood for
Residential Premiumhttp://USER-country-cl-sid-…:[email protected]:8080, SOCKS5 on 1080Most scripts: home IPs in the country, region, city or ISP in the username
ISPhttp://USER:PASS@<IP>:50100, one fixed IP per line, SOCKS5 on 50101Accounts and sessions that should keep the same address for months
Mobilehttp://USER-country-cl-type-mobile-sid-…:PASS@mobile.quantumproxies.io:8080, SOCKS5 on 1080Platforms that expect phone traffic

Residential Premium

Every example above is Residential Premium: the gateway stays the same and the username picks the exit, by country, region, city or ISP, rotating or sticky. The plans are on the pages for QuanticData residential proxies and Chile residential proxies.

ISP proxies

An ISP plan lists fixed IPs under Proxy endpoints, Assigned IPs. Choose the Protocol (HTTP / HTTPS uses port 50100, SOCKS5 port 50101) and the Output format IP:Port:Username:Password, then Copy selected or Export .txt. Each line becomes one agent, and each account or worker keeps its agent:

The Assigned IPs panel of a QuanticData ISP plan: Protocol HTTP / HTTPS or SOCKS5, Output format IP:Port:Username:Password, five IPs masked on port 50100 with username USER, and the formatted output ready to copy
ISP plan: one fixed IP per line. Sample IPs masked.
import { ProxyAgent } from 'undici';

// Assigned IPs, format IP:Port:Username:Password (one fixed IP per line)
const lines = [
  '104.•••.•••.91:50100:USER:PASS',
  '138.•••.•••.114:50100:USER:PASS',
  '154.•••.•••.137:50100:USER:PASS',
];

const agents = lines.map((line) => {
  const [ip, port, user, pass] = line.split(':');
  const auth = `${encodeURIComponent(user)}:${encodeURIComponent(pass)}`;
  return new ProxyAgent(`http://${auth}@${ip}:${port}`);
});

// the same account always leaves from the same IP: agents[0], agents[1]...
const res = await fetch('https://ipinfo.io/json', { dispatcher: agents[0] });
console.log(res.status, (await res.json()).ip);
VS Code with isp.mjs: three QuanticData ISP lines from Assigned IPs in the format IP:Port:Username:Password on port 50100, IPs masked, split at the colons, user and password URL-encoded, one undici ProxyAgent per fixed IP; five numbered steps beside it
ISP lines turned into one ProxyAgent per fixed IP. Redrawn screen, IPs masked.

With node-fetch the same URL goes into new HttpsProxyAgent(...) instead. A check through an ISP line shows the IP of the line itself, and it stays the same: there is nothing to rotate. More on the plans: ISP proxies.

Mobile proxies

Mobile lines work exactly like Residential Premium, on the mobile gateway and with -type-mobile in the username. In the generator choose the gateway (Global, USA, EU or Asia), the country, Sticky and HTTP on port 8080 or SOCKS5 on port 1080. Mobile plans also offer IP whitelist authentication: once your server's IP is added, the generator lists host:port lines with no login, and the agent takes just http://host:port.

import { ProxyAgent } from 'undici';

// Mobile: the same code, the mobile gateway and -type-mobile in the username
const proxy = 'http://USER-country-cl-type-mobile-sid-m01-ttl-30m:PASS@mobile.quantumproxies.io:8080';
const dispatcher = new ProxyAgent(proxy);

const res = await fetch('https://ipinfo.io/json', { dispatcher });
const { ip, city, region, country, org } = await res.json();
console.log(res.status, { ip, city, region, country, org });
200 {
  ip: '191.127.•••.•••',
  city: 'Santiago',
  region: 'Santiago Metropolitan',
  country: 'CL',
  org: 'AS7418 TELEFÓNICA CHILE S.A.'
}
The QuanticData Mobile Endpoint generator: Chile selected, Global, USA, EU and Asia gateways, Sticky session, HTTP on port 8080 or SOCKS5 on port 1080, the preview line with -type-mobile and the IP whitelist section, credentials blurred
Mobile: gateway, sticky session, ports and IP whitelist.

The plans are on the page for QuanticData mobile proxies. Browsers driven from Node take the proxy differently; see the guides for Puppeteer and Playwright.

Node.js fetch proxy errors and fixes

The most confusing one comes from mixing undici versions. Here npm install undici pulled the latest release, 8.12.3, while Node 24.21.0 runs its fetch on undici 7.29.1. The request fails before any connection is made:

[TypeError: fetch failed] {
  [cause]: InvalidArgumentError: invalid onRequestStart method
      at assertRequestHandler (/Users/dev/proxy-demo/node_modules/undici/lib/core/util.js:597:11)
      ...
    code: 'UND_ERR_INVALID_ARG'
  }
}
VS Code terminal with the real error TypeError: fetch failed, cause InvalidArgumentError: invalid onRequestStart method, code UND_ERR_INVALID_ARG; node -p process.versions.undici answers 7.29.1 and npm ls undici shows undici 8.12.3; beside it the fix: npm install undici@7, or import fetch and ProxyAgent from undici together
The undici version error as Node 24 prints it, and the two fixes. Redrawn screen with the real output.
What you seeCauseFix
fetch failed, cause invalid onRequestStart method, UND_ERR_INVALID_ARGundici from npm is a different major from the one inside Nodenpm install undici@ the major of process.versions.undici, or import fetch from undici too
The request leaves from your own IP although HTTPS_PROXY is setNODE_USE_ENV_PROXY is not set, or Node is older than 24.0.0 and 22.21.0Set NODE_USE_ENV_PROXY=1 or --use-env-proxy, or pass an agent
node-fetch: read ECONNRESET; undici: HTTPParserError: Response does not match the HTTP/1.1 protocolAn HTTP agent pointed at port 1080, the SOCKS5 portPort 8080 for http://, or socks5h:// with port 1080
InvalidArgumentError: invalid url when the ProxyAgent is createdThe password holds @, # or another reserved characterencodeURIComponent() on the username and password
Every request fails, and curl with the same values answers 407 Proxy Authentication RequiredUsername or password wrong, or pasted with a space or a line breakCopy the URL again from the generator's Preview; see fixing 407 Proxy Authentication Required
require() of node-fetch failsnode-fetch 3 is ES module onlyimport, a dynamic import(), or node-fetch 2
Wrong countryNo -country- in the usernameGenerate the URL with a country
The IP changes in the middle of a jobThe TTL ended, or two workers share a session IDA longer -ttl-, a different -sid- per worker

One test outside Node settles most cases. If this answers from the right country, the proxy works and the problem is in the script; more options in the guide to using a proxy with curl:

curl -x http://residentialpremium.quantumproxies.io:8080 \
  -U "USER-country-cl-sid-test-ttl-10m:PASS" https://ipinfo.io/json

Our run of that check on 24 October 2026 answered from Antofagasta, Antofagasta region, Chile, time zone America/Santiago.

Use proxies the way they are meant to be used

A proxy changes where a request comes from, not what a site allows. Keep to each site's terms and robots rules and to the law where you work. The Node.js documentation also reminds that a proxy can see connection metadata and, for plain HTTP, the content: send credentials and private data only over HTTPS and only through proxies you trust.

Sources & further reading

FAQ

Quick answers on node fetch proxy agent.

Something else? Ask us

How do I use a proxy with node-fetch?

Install https-proxy-agent, create new HttpsProxyAgent('http://user:pass@host:port') and pass it in the options: fetch(url, { agent }). For a SOCKS5 proxy use socks-proxy-agent with a socks5h:// URL instead. node-fetch 3 is an ES module, so load it with import.

Does Node's built-in fetch use HTTP_PROXY and HTTPS_PROXY?

Only when you turn it on. From Node 24.0.0 and 22.21.0, NODE_USE_ENV_PROXY=1 makes Node read HTTP_PROXY, HTTPS_PROXY and NO_PROXY; the --use-env-proxy flag does the same from 24.5.0 and 22.21.0. Without it, and on older versions, fetch ignores the variables and connects directly. On older versions, undici's EnvHttpProxyAgent reads them for you.

Why does fetch fail with "invalid onRequestStart method"?

The ProxyAgent comes from an undici release with a different major version from the undici inside Node. Run node -p process.versions.undici and install that major (undici 6 on Node 22, 7 on Node 24, 8 on Node 26), or import fetch and ProxyAgent from undici together so both come from the same package.

Can Node.js fetch use a SOCKS5 proxy?

Yes, through an agent: socks-proxy-agent with node-fetch, or undici's ProxyAgent with a socks5:// URL in recent releases, where SOCKS5 support is marked experimental. NODE_USE_ENV_PROXY only takes HTTP and HTTPS proxy URLs. Use socks5h:// with socks-proxy-agent so the proxy resolves the host name.

How do I keep the same IP for several requests?

Use a sticky session: the same -sid- in the username keeps the same exit IP for as long as its TTL lasts, up to 1440 minutes with QuanticData Residential Premium. Give each worker its own session ID and its own agent; a new session ID gives a new IP. ISP proxies are fixed IPs and never change.

Which undici version should I install for a proxy?

The major that matches Node when you pass the ProxyAgent to Node's own fetch: in October 2026 that is undici 6 for Node 22, 7 for Node 24 and 8 for Node 26. undici 8 itself needs Node 22.19 or later. If you import fetch from undici as well, the latest release works on any Node it supports.

A sticky IP for every Node.js worker

Residential Premium, ISP and mobile proxies with country targeting and sticky sessions up to 1440 minutes, HTTP and SOCKS5, copied from the endpoint generator as one URL for node-fetch or fetch.

Related reading