How to Fix node-fetch Proxy Not Working
Three fixes for node-fetch 2 and 3, most common first, each with a short Node.js example.
Updated
TL;DR
node-fetch has no proxy setting of its own, so a call made without an agent normally leaves from your own IP and nothing warns you. Create an HttpsProxyAgent from https-proxy-agent and pass it as the agent option of each fetch call.
What node-fetch proxy problems look like
A skipped proxy throws nothing; these are the messages around it. Paths and addresses vary.
| Where | What you see |
|---|---|
| Node.js 20.18 and older or 22.0 to 22.11, require('node-fetch') with node-fetch 3 | Error [ERR_REQUIRE_ESM]: require() of ES Module .../node_modules/node-fetch/src/index.js from .../app.js not supported. |
| Node.js 20.19+ and 22.12+, const fetch = require('node-fetch') with node-fetch 3 | TypeError: fetch is not a function |
| https-proxy-agent 7 loaded the version 5 way | TypeError: HttpsProxyAgent is not a constructor |
| node-fetch with an agent, nothing listening on the proxy port | FetchError: request to https://example.com/ failed, reason: connect ECONNREFUSED 127.0.0.1:8080 |
| node-fetch with https-proxy-agent, proxy refuses the login (res.status and res.statusText, nothing thrown) | 407 Proxy Authentication Required |
Why this happens
node-fetch hands the connection to Node.js, which dials the site directly.
Under the hood each call uses Node.js's built-in client, and node-fetch passes it one connection setting, the agent. Leave that out and the global default normally connects from your machine, whatever proxy your browser or shell uses.
Diagnose your node-fetch proxy first
The first check needs only an editor; the rest show whether requests reach the proxy at all.
Open package.json; a node-fetch entry starting with ^3 is an ESM-only release, so CommonJS require() calls break.
Search the code for each fetch( call; an options object lacking agent sends that request around the proxy.
Note your IP on What is my IP, then request an IP echo URL from the script on that machine; a match means no proxy.
Print res.status and res.statusText; 407 with nothing thrown means the agent reached the proxy but the login was refused.
Read the address after ECONNREFUSED in the FetchError; your proxy's IP and port there mean the agent was used but nothing answered at that address.
Solutions ranked by effectiveness
Most scripts need only the first fix; the other two cover node-fetch 3 and proxies set in the environment.
- Most common fix
Pass an HttpsProxyAgent as the agent
Applies to version 2 in CommonJS projects. Build the agent once from your proxy URL and reuse it; import the class by name, since release 7 has no default export.
// npm install node-fetch@2 https-proxy-agent@7 const fetch = require("node-fetch"); const { HttpsProxyAgent } = require("https-proxy-agent"); const proxy = "http://USERNAME:PASSWORD@proxy.geonode.io:9000"; const agent = new HttpsProxyAgent(proxy); fetch("https://example.com/", { agent }) .then((res) => console.log("status", res.status)) .catch((err) => console.log(err.name, err.message)); - Check next
Load node-fetch 3 with import()
Applies when loading throws ERR_REQUIRE_ESM or says fetch is not a function. Release 3 ships only as an ES module, so CommonJS reaches its default export through dynamic import(); downgrading to @2 also works.
// npm install node-fetch@3 https-proxy-agent@7 const { HttpsProxyAgent } = require("https-proxy-agent"); const proxy = "http://USERNAME:PASSWORD@proxy.geonode.io:9000"; const agent = new HttpsProxyAgent(proxy); import("node-fetch") .then(({ default: fetch }) => fetch("https://example.com/", { agent })) .then((res) => console.log("status", res.status)) .catch((err) => console.log(err.name, err.message)); - For developers
Let proxy-agent read HTTPS_PROXY and NO_PROXY
Applies when the proxy URL should come from environment variables. proxy-agent picks an agent per URL from them; start the script with HTTPS_PROXY=http://USERNAME:PASSWORD@proxy.geonode.io:9000.
// npm install node-fetch@2 proxy-agent@6 const fetch = require("node-fetch"); const { ProxyAgent } = require("proxy-agent"); const agent = new ProxyAgent(); fetch("https://example.com/", { agent }) .then((res) => console.log("status", res.status)) .catch((err) => console.log(err.name, err.message));
Stop the node-fetch proxy problem from coming back
Habits that keep every call on the proxy through package updates, new servers and password changes.
Pin node-fetch to ^2 and https-proxy-agent to ^7 in package.json; their next majors load only as ES modules.
Wrap USERNAME and PASSWORD in encodeURIComponent when building the proxy URL; the agent decodes both before sending them.
With proxy-agent, set HTTPS_PROXY too: https:// URLs never read HTTP_PROXY, and with no match they connect directly.
Let retry logic treat a 502 response as possibly the proxy's, since https-proxy-agent passes CONNECT failures through as responses.
Related errors
Learn more
FAQ
One sticky IP per agent
On sticky ports, a session ID in each agent's username keeps its IP until expiry.


