Node.js Fetch Proxy Setup: Native Settings and Undici
Choose native environment routing or a matched Undici client, then verify authentication, HTTPS and bypass behavior in your Node.js worker.
Before running the code
- Node.js proxy environment support is version-dependent and opt-in. Setting HTTPS_PROXY alone is not a portable configuration.
- Keep fetch and ProxyAgent compatible. Our Node 22 test caught an incompatibility when mixing built-in fetch with Undici 8.
- This is server-side JavaScript. Browser fetch cannot select a proxy through a Node.js dispatcher.
Choose one routing mechanism
| Situation | Configuration | Verify |
|---|---|---|
| Supported Node runtime, whole-worker routing | NODE_USE_ENV_PROXY=1 or --use-env-proxy | HTTP_PROXY, HTTPS_PROXY and intended NO_PROXY entries. |
| One integration needs an explicit proxy | fetch and ProxyAgent from the same Undici install | Package version and request-level dispatcher. |
| Browser-side JavaScript | Browser or operating-system proxy configuration | The browser network route, not a Node option. |
| SDK creates its own HTTP client | SDK-supported client injection | Actual tool requests, not only a separate smoke test. |
There are two useful starting points. Use native environment support when the worker has a supported Node.js release and centrally managed egress settings. Use an explicit Undici dispatcher when a particular integration needs its own endpoint and credentials.
Node.js documents native environment proxy support for fetch() from v22.21.0 and v24.0.0, and the --use-env-proxy flag from v22.21.0 and v24.5.0. Its http and https support has its own version boundary. Check the actual worker with node --version; the version used by your build tool may differ.
Do not copy Axios's proxy option or a traditional http.Agent into fetch and assume it is equivalent. Node's fetch API describes the Undici dispatcher extension. For Axios applications, keep the Axios proxy guide as the implementation reference.
Use native proxy environment support
For a supported runtime, supply the authenticated gateway URL privately as HTTP_PROXY and HTTPS_PROXY, then start the process with proxy support enabled:
node --use-env-proxy worker.mjs
A minimal worker.mjs can use built-in fetch:
const response = await fetch(process.env.TARGET_URL || 'https://example.com/', {
redirect: 'manual',
signal: AbortSignal.timeout(20_000),
});
console.log(response.status);
await response.body?.cancel();
Choose NO_PROXY deliberately. An entry for the destination can turn a successful connectivity check into a direct request. Test an intended bypass and a destination that must traverse the gateway. Keep credentials in the service's environment configuration rather than a command pasted into shell history.
Our fixture ran authenticated native environment routing and a deliberate bypass on Node.js 22.23.1. This does not establish the behavior of an older production runtime.
Use matching Undici imports for explicit routing
Install the Undici version compatible with your Node runtime in the worker project and retain its lockfile. Our example was tested with Node.js 22.23.1 and Undici 8.12.1. The gateway endpoint contains no embedded credentials; the two secret fields form a proxy-specific Basic token.
// Server-side Node.js. Set PROXY_URL, PROXY_USER and PROXY_PASSWORD privately.
import { fetch, ProxyAgent } from 'undici';
let dispatcher;
try {
for (const name of ['PROXY_URL', 'PROXY_USER', 'PROXY_PASSWORD']) {
if (!process.env[name]) throw new Error(`Missing ${name}`);
}
const token = Buffer.from(`${process.env.PROXY_USER}:${process.env.PROXY_PASSWORD}`).toString('base64');
dispatcher = new ProxyAgent({
uri: process.env.PROXY_URL,
token: `Basic ${token}`,
connections: 2,
});
const response = await fetch(process.env.TARGET_URL || 'https://example.com/', {
dispatcher,
redirect: 'manual',
signal: AbortSignal.timeout(20_000),
});
console.log(`HTTP ${response.status}`);
if (!response.ok) {
await response.body?.cancel();
process.exitCode = 1;
} else {
const body = await response.arrayBuffer();
console.log(`Received ${body.byteLength} bytes`);
}
} catch (error) {
// Do not log the raw error, request options or credential-bearing URLs.
console.error(`Request failed: ${error.name}`);
process.exitCode = 1;
} finally {
await dispatcher?.close();
}
Download the Node.js example. ProxyAgent's documentation defines the token and dispatcher options. Base64 encoding is not encryption; protect the hop to the proxy according to your network requirements. Destination HTTPS remains a separate TLS connection.
The sample reads a small response into memory. For an export or long stream, process the body incrementally, cap the accepted bytes and propagate cancellation. Closing the dispatcher releases its connections after the request.
The compatibility failure we reproduced
During the local checks, pairing Node.js 22.23.1's built-in fetch with a ProxyAgent from Undici 8.12.1 failed before contacting the proxy. The nested error reported invalid onRequestStart method. The test passed after importing both fetch and ProxyAgent from the same Undici installation, as the full example does.
That observation is specific to the versions we tested. It is not a claim that every custom dispatcher is incompatible with built-in fetch. When a dependency update breaks routing, check the dispatch interface and package versions before resetting a proxy password or changing carriers.
An integration's transitive dependencies can also contain a different Undici version. Keep the creation and use of a dispatcher in the same integration boundary. Record the runtime, lockfile and minimal failing request in an issue so someone else can reproduce it.
When EnvHttpProxyAgent is useful
EnvHttpProxyAgent reads proxy and bypass settings for Undici requests. Constructing it alone does not reroute requests; pass it as a dispatcher or deliberately register it globally. The documented lowercase proxy variables take precedence over their uppercase equivalents when both exist.
Use the same-package imports here too:
import { fetch, EnvHttpProxyAgent } from 'undici';
const dispatcher = new EnvHttpProxyAgent();
try {
const response = await fetch('https://example.com/', { dispatcher });
console.log(response.status);
await response.body?.cancel();
} finally {
await dispatcher.close();
}
This small configuration sketch was source-reviewed; the downloadable explicit ProxyAgent script and native environment path were the ones exercised by our fixture. In a production tool server, prefer a request-level dispatcher when only one integration should use the proxy. A global dispatcher change can affect unrelated requests using the same global mechanism.
Keep routing failures separate from website refusals
A fetch promise can reject for connection or TLS errors. An HTTP response such as 403 or 429 normally arrives as a response object; check the status explicitly. The full example cancels an unsuccessful body and exits without an automatic retry.
For a 407 during CONNECT, check the gateway credential or allowed source IP. For an untrusted certificate, inspect the certificate chain and trusted CA setup. Never use NODE_TLS_REJECT_UNAUTHORIZED=0 to make a production proxy test pass. For a 429, preserve the destination's delay and reduce the workload; a new mobile IP is not permission to ignore a rate limit.
A proxy route also does not change a browser fingerprint or grant access to an account. Compare the proxy error reference and IP reputation guide when transport and destination behavior disagree.
Wire the HTTP tool that actually runs
In a retrieval or MCP worker, give the fetch wrapper a fixed dispatcher and a destination policy. Avoid taking a complete authenticated proxy URL from model-visible arguments. Keep the connection secret in runtime configuration and redact it from traces.
If the tool launches Playwright, that browser requires its own proxy settings. If the worker lives in a cloud environment, verify its outbound source address; a developer laptop's address cannot authorize a different machine. These boundaries matter more than whether a workflow is described as an AI agent or an ordinary scheduled task.
Published example checks
Our October 14 local fixture verified authenticated HTTP and HTTPS CONNECT, a rejected proxy password, rejection of an untrusted TLS certificate, no redirect follow and no retry after a 429. It checked that origin requests did not receive proxy credentials. Native environment routing and an intentional NO_PROXY bypass were also tested.
This is a configuration test using synthetic credentials on localhost, not a mobile-network benchmark or a test against a site's anti-bot controls. Verify the production route with your own endpoint before measuring throughput or buying more capacity.
Sources and review scope
Sources reviewed October 14, 2026. This guide reviews primary documentation. Code examples illustrate configuration and error handling. Any local fixture checks are described in the article; they are not live-provider performance benchmarks or guarantees of destination access.
Frequently asked questions
Configure another runtime
Use the settings supported by the process making the request, then verify routing and authentication.
Related workflows
Match an authorized collector to its network and session requirements.
Compare available carrier plans when your integration needs a US mobile exit.
Configure the Python client separately in a mixed-runtime application.
Identify the worker address presented to the gateway.