The starting point
Use a dispatcher for one fetch call so the proxy is explicit and other network clients in your process keep their own configuration.
Choose a compatible runtime and dispatcher
This server-side example targets Node.js 24 and Undici 7. In a separate example project, run npm install undici@7. Node's built-in fetch accepts an Undici-compatible dispatcher. This is different from browser fetch, where page JavaScript cannot select an arbitrary system proxy.
Use a provider-documented HTTP(S) gateway in PROXY_URL, for example the deliberately nonworking https://proxy.example.invalid:8443. Inject PROXY_USERNAME and PROXY_PASSWORD through your deployment's secret manager. Do not put gateway credentials in frontend code.
Keep proxy authentication on the dispatcher
Save as proxy-check.mjs and run node proxy-check.mjs. The Basic token belongs to the proxy agent, not to the destination's Authorization header. The request has a 30-second abort deadline and does not follow redirects automatically.
import { ProxyAgent } from 'undici';
function required(name) {
const value = process.env[name];
if (!value) throw new Error('Missing environment variable: ' + name);
return value;
}
const gateway = new URL(required('PROXY_URL'));
if (!['http:', 'https:'].includes(gateway.protocol) || gateway.username || gateway.password) {
throw new Error('Use a credential-free HTTP(S) proxy URL');
}
const credentials = required('PROXY_USERNAME') + ':' + required('PROXY_PASSWORD');
const dispatcher = new ProxyAgent({
uri: gateway.href,
token: 'Basic ' + Buffer.from(credentials).toString('base64'),
});
try {
const response = await fetch('https://example.com/', {
dispatcher,
signal: AbortSignal.timeout(30_000),
redirect: 'manual',
});
await response.body?.cancel();
if (!response.ok) throw new Error('Destination HTTP ' + response.status);
console.log({ status: response.status });
} finally {
await dispatcher.close();
}Move from a diagnostic to a worker
This script deliberately cancels the response body because the diagnostic needs only a status. A worker that uses the result should consume the body within its budget. Always consume or cancel it before moving on, and close the dispatcher when the worker shuts down.
For repeated jobs, reuse a dispatcher with bounded concurrency rather than creating one per request. Keep each job's deadline separate. Do not install a process-wide dispatcher unless changing every affected client's routing is intentional.
- An HTTP error is a response; fetch does not reject merely because its status is 4xx or 5xx.
- A CONNECT rejection can surface as a fetch failure before a destination Response exists.
- Capture sanitized error categories; do not log the Basic token or full credential-bearing URLs.
From reading to doing
Before you ship
- Use the documented Node and Undici major versions.
- Pass the dispatcher explicitly.
- Consume or cancel response bodies and close the agent.
Sources & further reading
Technical references used for this guide. Check the documentation for your installed version and your provider’s supported configuration.