How to use a proxy in Node.js (axios, fetch and got)
For axios, create an HttpsProxyAgent with http://GATEWAY_IP:PORT, pass it as httpsAgent and set proxy: false. For native fetch, pass an undici ProxyAgent as the dispatcher. For got, use an hpagent agent. Then cap parallel requests with p-limit at your plan’s thread count.
Node.js has no single HTTP client. Most scrapers and integrations use axios, the built-in fetch (powered by undici since Node 18), or got. Each one handles proxies differently, and the differences are where people lose hours: an option that works for http:// URLs and silently misbehaves for https://, a dispatcher that the global fetch rejects, or a keep-alive pool that pins every request to the same exit IP.
The other common failure is concurrency. A Promise.all over 5,000 URLs opens 5,000 sockets at once, which no proxy plan will accept. Your plan limits how many connections can be open at the same moment, so the code has to respect that number.
This guide gives a working setup for each client, explains why axios’ own proxy option was a trap for HTTPS sites, shows how to control rotation through keep-alive, and adds a p-limit pattern that keeps you inside your thread limit. Every snippet uses the Storm gateway format, and most of it applies to any HTTP proxy.
Which Storm plan fits
Node scrapers fit rotating proxies well: the Main gateway hands out a fresh exit IP per connection, and pricing is per thread, so a crawler that downloads gigabytes costs the same as one that downloads megabytes. For logged-in flows that need one IP for a while, use the 3- or 15-minute gateway or a residential port. If your Node code runs on serverless or another host with a changing outgoing IP, pick private dedicated proxies with username and password instead.
Rotating proxies (from $14/mo): 700,000+ IPs behind fixed gateway IP:PORTs. New IP on every request, or every 3 or 15 minutes. USA, EU, USA+EU or Worldwide. Unlimited bandwidth on every plan.
Get 40 threads for $39/mo See all rotating proxies plansBefore you start
- Log in to the member area and copy your gateway
IP:PORTs. They never change; the rotation happens on our side. - Add the public IP of the computer or server that will run your tool under Authorized IPs, click Save, and allow up to 15 minutes before testing. Rotating and residential proxies use IP authentication, so there is no username or password.
- Dedicated proxies work with either IP authentication or a username and password. Use user:pass if your IP changes or the tool runs on several machines.
- Count your threads: the tool’s total open connections must stay within your plan (for example 40 threads on the 40-thread plan).
- Set up a proxy in Node.js, step by step
- Copy-paste examples
- Why axios’ built-in proxy option fails on HTTPS sites
- Keep-alive decides how often your IP changes
- Concurrency: map threads to p-limit
- Headless browsers and JavaScript-heavy sites
- Running Node on servers, CI and serverless
- Common errors and fixes
- FAQ
Set up a proxy in Node.js, step by step
- Check your Node version
Run
node -v. Globalfetchexists from Node 18. Node 22.21+ and 24.5+ also have built-in support forHTTP_PROXY/HTTPS_PROXYwhen you setNODE_USE_ENV_PROXY=1or start Node with--use-env-proxy. On older versions you need an agent package. - Install the agent for your client
axios:
npm i axios https-proxy-agent. fetch:npm i undici. got:npm i got hpagent. For concurrency control addnpm i p-limit. Recent versions of these packages are ESM, so useimportor a.mjsfile. - Authorize the machine that runs Node
Rotating and residential gateways accept connections only from IPs on your Authorized IPs list. Find the public IP of the server running your script (not your laptop, if the script runs elsewhere), save it in the member area, and give it up to 15 minutes.
- Build the proxy URL
Use
http://GATEWAY_IP:PORT. Keep thehttp://scheme even when every target is an HTTPS site: it describes the hop to the proxy, and the agent opens a CONNECT tunnel through which TLS runs to the website. For dedicated proxies with credentials, usehttp://USER:PASS@PROXY_IP:PORTand run the password throughencodeURIComponent. - Wire the agent into the client
axios gets
httpsAgent(andhttpAgentfor plain-HTTP targets) plusproxy: false. fetch getsdispatcher. got getsagent: {http, https}. The code below shows all three. - Verify the exit IP
Request
https://httpbin.org/iporhttps://api.ipify.org?format=json. You should see a proxy IP. Run it three times without keep-alive on the Main gateway and you should see three different IPs. - Add a concurrency limit
Wrap each request in
limit(() => ...)from p-limit, with the limit at or below your plan’s threads. That one line is the difference between a stable crawl and a wall ofECONNRESET.
Copy-paste examples
Replace GATEWAY_IP:PORT with a gateway from your member area. Save files as .mjs (or set "type": "module" in package.json) so the import lines work.
import axios from "axios";
import { HttpsProxyAgent } from "https-proxy-agent";
import { HttpProxyAgent } from "http-proxy-agent"; // npm i http-proxy-agent (only for http:// targets)
const PROXY = "http://GATEWAY_IP:PORT"; // http:// even for https sites
const client = axios.create({
httpsAgent: new HttpsProxyAgent(PROXY),
httpAgent: new HttpProxyAgent(PROXY),
proxy: false, // stop axios from adding its own proxy logic or reading HTTPS_PROXY
timeout: 30000,
});
const { data } = await client.get("https://httpbin.org/ip");
console.log(data); // proxy IP, not yoursimport { fetch, ProxyAgent } from "undici"; // use undici's fetch with undici's agent
const dispatcher = new ProxyAgent("http://GATEWAY_IP:PORT");
const res = await fetch("https://httpbin.org/ip", {
dispatcher,
signal: AbortSignal.timeout(30000),
});
console.log(await res.json());
// Node 22.21+ / 24.5+: no package needed
// NODE_USE_ENV_PROXY=1 HTTPS_PROXY=http://GATEWAY_IP:PORT node app.mjsimport got from "got";
import { HttpsProxyAgent, HttpProxyAgent } from "hpagent";
const proxy = "http://GATEWAY_IP:PORT";
const agent = {
https: new HttpsProxyAgent({ proxy, keepAlive: false }),
http: new HttpProxyAgent({ proxy, keepAlive: false }),
};
const body = await got("https://httpbin.org/ip", { agent, timeout: { request: 30000 } }).json();
console.log(body);import axios from "axios";
import { HttpsProxyAgent } from "https-proxy-agent";
const user = "USERNAME";
const pass = encodeURIComponent("PASSWORD"); // handles @ : / # in passwords
const agent = new HttpsProxyAgent(`http://${user}:${pass}@PROXY_IP:PORT`);
const { data } = await axios.get("https://httpbin.org/ip", { httpsAgent: agent, proxy: false });
console.log(data);
// undici: new ProxyAgent({ uri: "http://PROXY_IP:PORT",
// token: "Basic " + Buffer.from("USERNAME:PASSWORD").toString("base64") })import axios from "axios";
import pLimit from "p-limit";
import { HttpsProxyAgent } from "https-proxy-agent";
const THREADS = 40; // your plan's thread count, or lower
const limit = pLimit(THREADS);
const agent = new HttpsProxyAgent("http://GATEWAY_IP:PORT", { keepAlive: false });
const client = axios.create({ httpsAgent: agent, proxy: false, timeout: 30000 });
async function fetchOne(url, tries = 3) {
for (let i = 1; i <= tries; i++) {
try {
const r = await client.get(url);
return { url, status: r.status };
} catch (e) {
if (i === tries) return { url, error: e.code || e.message };
await new Promise(r => setTimeout(r, 1000 * 2 ** (i - 1))); // 1s, 2s backoff
}
}
}
const urls = Array.from({ length: 500 }, (_, i) => `https://httpbin.org/anything/${i}`);
const results = await Promise.all(urls.map(u => limit(() => fetchOne(u))));
console.log(results.filter(r => r.error).length, "failed");Why axios’ built-in proxy option fails on HTTPS sites
axios has a proxy: {protocol, host, port} option, and almost every tutorial starts with it. For http:// targets it works. For https:// targets, axios versions before 1.16.1 (May 2026) did not open a CONNECT tunnel. They sent the request to the proxy in forward-proxy form instead, which meant two things: many proxies answered with 400, 405 or a TLS error, and where it did work, the proxy could read the full HTTPS request. The maintainers fixed this in 1.16.1 by tunnelling HTTPS through CONNECT.
You will still meet old axios versions in lockfiles and in dependencies of other packages, so the safe pattern is the one in this guide: give axios an HttpsProxyAgent as httpsAgent and set proxy: false. The agent does the CONNECT, and proxy: false stops axios from stacking its own logic on top (or from reading an HTTPS_PROXY variable you forgot was set). This works on every axios version.
Two mistakes to avoid: putting the HTTPS agent under httpAgent (it is chosen by the target URL’s scheme, so https sites use httpsAgent), and writing the proxy URL as https://GATEWAY_IP:PORT, which makes the agent try TLS to the gateway itself.
Keep-alive decides how often your IP changes
On the Main rotating gateway the exit IP is picked when a connection (for HTTPS, a CONNECT tunnel) is opened. Every request that reuses that socket leaves from the same IP. In Node, socket reuse is controlled by the agent:
- https-proxy-agent / hpagent: pass
keepAlive: falsefor a new tunnel, and so a new IP, per request. PasskeepAlive: trueto hold an IP across a multi-step flow and to save TLS handshakes. - undici ProxyAgent: pools connections by default, so a long-lived dispatcher tends to reuse tunnels. Create one dispatcher per logical session (one per account or per task) when you want separate IPs, and call
await dispatcher.close()when that session ends. - Node’s own global agent keeps connections alive by default in Node 19 and later, which surprises people upgrading from Node 18.
If you need the same IP for minutes, don’t fight the Main gateway with sockets: point that client at the 3-minute or 15-minute gateway, or at a residential port, which keeps one IP for its 5-minute window.
Concurrency: map threads to p-limit
Your thread count is the number of connections that may be open at the same time across everything using the plan. In Node, async code makes it very easy to blow past that: await Promise.all(urls.map(get)) starts every request immediately. Use one of these caps:
- p-limit (shown above) caps in-flight promises. Set it to your thread count, or lower if a second script or a browser shares the plan.
- Agent sockets:
maxSocketson an http agent, orconnectionson an undici agent, caps sockets per origin. It’s a useful second guard, but it queues silently, so p-limit is easier to reason about.
On the 40-thread plan, run 35 to 40 workers. When scraping Google or another search engine, stay at or below 25% of your threads on the Main gateway (10 on a 40-thread plan). With residential, each port allows up to 50 threads that all share one IP, so p-limit per port and spread work across ports when you need many IPs at once.
Headless browsers and JavaScript-heavy sites
axios, fetch and got download raw HTML. Pages that render their content in the browser come back as an empty shell. For those, drive a browser from Node instead and pass the same gateway to it: see the Puppeteer guide or the Playwright guide. Budget threads differently there: one browser tab can open around 10 connections on its own, so a 40-thread plan supports about four parallel tabs, not forty.
Running Node on servers, CI and serverless
Rotating and residential plans use IP authorization, so the gateway must see a known source IP. A VPS, a dedicated server or your own machine works. AWS Lambda, Vercel or Netlify functions, many CI runners and hosted sandboxes use outgoing IPs that change between runs, so the gateway will refuse them.
Two fixes: run the scraper on a small server with a fixed IP and authorize it, or use private dedicated proxies, which accept user:pass in the proxy URL from anywhere. For a home connection whose IP changes, authorize a free dynamic DNS hostname such as No-IP. For a quick shell-level test of the same gateway, the cURL proxy guide has one-line checks.
Common errors and fixes
connect ECONNREFUSED GATEWAY_IP:PORTNothing is listening on that IP and port: a typo in the gateway address or port, or a firewall. Copy the gateway again from the member area. This isn’t an authorization problem.400 Bad Request, 405 or TLS errors with axios’ proxy optionAn axios version older than 1.16.1 forwarding HTTPS instead of tunnelling. Switch to httpsAgent: new HttpsProxyAgent(...) with proxy: false, or upgrade axios.EPROTO / wrong version numberThe proxy URL starts with https://. Use http://GATEWAY_IP:PORT; the target site stays HTTPS.TypeError: fetch failed with UND_ERR_INVALID_ARG or a dispatcher errorYou passed an agent from the npm undici package to Node’s global fetch, and the versions don’t match. Import fetch and ProxyAgent from the same undici package. Always log err.cause, since “fetch failed” hides the real reason.ECONNRESET / socket hang upOn every request from the start: the machine running Node isn’t on Authorized IPs, or the save is less than 15 minutes old; the gateway accepts the connection and resets it. Now and then: too many parallel sockets for your plan, or a residential port rotating its IP mid-request (at minute 1, 6, 11… of the hour). Cap with p-limit and retry with backoff.407 Proxy Authentication RequiredDedicated proxy credentials are wrong or not URL-encoded. On rotating or residential plans an unauthorized IP gives ECONNRESET, not a 407, so a 407 there comes from another proxy in the chain. See the 407 guide.ETIMEDOUT / UND_ERR_CONNECT_TIMEOUTRequests queued behind a full plan or a slow target. Lower concurrency, set a timeout on every request and retry once on a new connection.FAQ
Do Storm rotating proxies need a username and password in axios?
No. Rotating and residential gateways authorize your IP, so the agent URL is just http://GATEWAY_IP:PORT. Only private dedicated proxies can take user:pass in the URL.
Can I run my Node scraper on AWS Lambda or Vercel with Storm rotating proxies?
Usually not, because those platforms send traffic from changing IPs and rotating/residential plans authorize by IP. Run the job on a VPS with a fixed IP, or use dedicated proxies with username and password, which work from anywhere.
Is https-proxy-agent still needed with the latest axios?
axios 1.16.1 and later tunnel HTTPS through CONNECT on their own. The agent pattern is still the safer default because it behaves the same on every version and gives you control over keep-alive, which controls IP rotation.
Does Node’s built-in fetch support proxies without packages?
Yes, from Node 22.21 and 24.5: set NODE_USE_ENV_PROXY=1 (or use --use-env-proxy) and the usual HTTPS_PROXY/NO_PROXY variables. It applies one proxy to the whole process; for per-request control use an undici ProxyAgent.
Can I use SOCKS proxies in Node with Storm?
No. Storm gateways are HTTP(S) proxies only, so use https-proxy-agent, hpagent or undici, not a SOCKS agent.
Still have questions? Contact us here. A real person answers.
Related guides
Tool facts checked against the official documentation (October 2026): axios request config · axios v1.16.1 release notes · https-proxy-agent · undici ProxyAgent · Node.js HTTP: built-in proxy support · got: proxy tips · p-limit. Storm Proxies facts: our plans page and refund policy.
Unlimited bandwidth. One flat monthly price.
Access is live the moment you pay, and the smallest package of each proxy type has a 24-hour money-back guarantee on your first order.