Skip to content

cli: add --use-env-proxy #59151

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jul 26, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 20 additions & 2 deletions doc/api/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -2999,6 +2999,21 @@ environment variables.

See `SSL_CERT_DIR` and `SSL_CERT_FILE`.

### `--use-env-proxy`

<!-- YAML
added: REPLACEME
-->

> Stability: 1.1 - Active Development

When enabled, Node.js parses the `HTTP_PROXY`, `HTTPS_PROXY` and `NO_PROXY`
environment variables during startup, and tunnels requests over the
specified proxy.

This is equivalent to setting the [`NODE_USE_ENV_PROXY=1`][] environment variable.
When both are set, `--use-env-proxy` takes precedence.

### `--use-largepages=mode`

<!-- YAML
Expand Down Expand Up @@ -3498,6 +3513,7 @@ one is included in the list below.
* `--track-heap-objects`
* `--unhandled-rejections`
* `--use-bundled-ca`
* `--use-env-proxy`
* `--use-largepages`
* `--use-openssl-ca`
* `--use-system-ca`
Expand Down Expand Up @@ -3653,8 +3669,8 @@ When enabled, Node.js parses the `HTTP_PROXY`, `HTTPS_PROXY` and `NO_PROXY`
environment variables during startup, and tunnels requests over the
specified proxy.

This currently only affects requests sent over `fetch()`. Support for other
built-in `http` and `https` methods is under way.
This can also be enabled using the [`--use-env-proxy`][] command-line flag.
When both are set, `--use-env-proxy` takes precedence.

### `NODE_V8_COVERAGE=dir`

Expand Down Expand Up @@ -3984,12 +4000,14 @@ node --stack-trace-limit=12 -p -e "Error.stackTraceLimit" # prints 12
[`--print`]: #-p---print-script
[`--redirect-warnings`]: #--redirect-warningsfile
[`--require`]: #-r---require-module
[`--use-env-proxy`]: #--use-env-proxy
[`AsyncLocalStorage`]: async_context.md#class-asynclocalstorage
[`Buffer`]: buffer.md#class-buffer
[`CRYPTO_secure_malloc_init`]: https://www.openssl.org/docs/man3.0/man3/CRYPTO_secure_malloc_init.html
[`ERR_INVALID_TYPESCRIPT_SYNTAX`]: errors.md#err_invalid_typescript_syntax
[`ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX`]: errors.md#err_unsupported_typescript_syntax
[`NODE_OPTIONS`]: #node_optionsoptions
[`NODE_USE_ENV_PROXY=1`]: #node_use_env_proxy1
[`NO_COLOR`]: https://no-color.org
[`Web Storage`]: https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API
[`YoungGenerationSizeFromSemiSpaceSize`]: https://chromium.googlesource.com/v8/v8.git/+/refs/tags/10.3.129/src/heap/heap.cc#328
Expand Down
2 changes: 1 addition & 1 deletion doc/api/errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -2501,7 +2501,7 @@ Failed to proxy a request because the proxy configuration is invalid.

### `ERR_PROXY_TUNNEL`

Failed to establish proxy tunnel when `NODE_USE_ENV_PROXY` is enabled.
Failed to establish proxy tunnel when `NODE_USE_ENV_PROXY` or `--use-env-proxy` is enabled.

<a id="ERR_QUIC_APPLICATION_ERROR"></a>

Expand Down
16 changes: 11 additions & 5 deletions doc/api/http.md
Original file line number Diff line number Diff line change
Expand Up @@ -4273,10 +4273,9 @@ added: REPLACEME

> Stability: 1.1 - Active development

When Node.js creates the global agent, it checks the `NODE_USE_ENV_PROXY`
environment variable. If it is set to `1`, the global agent will be constructed
When Node.js creates the global agent, if the `NODE_USE_ENV_PROXY` environment variable is
set to `1` or `--use-env-proxy` is enabled, the global agent will be constructed
with `proxyEnv: process.env`, enabling proxy support based on the environment variables.

Custom agents can also be created with proxy support by passing a
`proxyEnv` option when constructing the agent. The value can be `process.env`
if they just want to inherit the configuration from the environment variables,
Expand Down Expand Up @@ -4318,13 +4317,20 @@ Multiple entries should be separated by commas.

### Example

Starting a Node.js process with proxy support enabled for all requests sent
through the default global agent:
To start a Node.js process with proxy support enabled for all requests sent
through the default global agent, either use the `NODE_USE_ENV_PROXY` environment
variable:

```console
NODE_USE_ENV_PROXY=1 HTTP_PROXY=http://proxy.example.com:8080 NO_PROXY=localhost,127.0.0.1 node client.js
```

Or the `--use-env-proxy` flag.

```console
HTTP_PROXY=http://proxy.example.com:8080 NO_PROXY=localhost,127.0.0.1 node --use-env-proxy client.js
```

To create a custom agent with built-in proxy support:

```cjs
Expand Down
3 changes: 3 additions & 0 deletions doc/node-config-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -541,6 +541,9 @@
"use-bundled-ca": {
"type": "boolean"
},
"use-env-proxy": {
"type": "boolean"
},
"use-largepages": {
"type": "string"
},
Expand Down
3 changes: 3 additions & 0 deletions doc/node.1
Original file line number Diff line number Diff line change
Expand Up @@ -603,6 +603,9 @@ See
and
.Ev SSL_CERT_FILE .
.
.It Fl -use-env-proxy
Parse proxy settings from HTTP_PROXY/HTTPS_PROXY/NO_PROXY environment variables and apply the setting in global HTTP/HTTPS clients.
.
.It Fl -use-largepages Ns = Ns Ar mode
Re-map the Node.js static code to large memory pages at startup. If supported on
the target system, this will cause the Node.js static code to be moved onto 2
Expand Down
4 changes: 3 additions & 1 deletion lib/_http_agent.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ const {
validateString,
} = require('internal/validators');
const assert = require('internal/assert');
const { getOptionValue } = require('internal/options');

const kOnKeylog = Symbol('onkeylog');
const kRequestOptions = Symbol('requestOptions');
Expand Down Expand Up @@ -622,6 +623,7 @@ module.exports = {
Agent,
globalAgent: new Agent({
keepAlive: true, scheduling: 'lifo', timeout: 5000,
proxyEnv: process.env.NODE_USE_ENV_PROXY ? filterEnvForProxies(process.env) : undefined,
// This normalized from both --use-env-proxy and NODE_USE_ENV_PROXY settings.
proxyEnv: getOptionValue('--use-env-proxy') ? filterEnvForProxies(process.env) : undefined,
}),
};
4 changes: 3 additions & 1 deletion lib/https.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ const { URL, urlToHttpOptions, isURL } = require('internal/url');
const { validateObject } = require('internal/validators');
const { isIP, isIPv6 } = require('internal/net');
const assert = require('internal/assert');
const { getOptionValue } = require('internal/options');

function Server(opts, requestListener) {
if (!(this instanceof Server)) return new Server(opts, requestListener);
Expand Down Expand Up @@ -599,7 +600,8 @@ Agent.prototype._evictSession = function _evictSession(key) {

const globalAgent = new Agent({
keepAlive: true, scheduling: 'lifo', timeout: 5000,
proxyEnv: process.env.NODE_USE_ENV_PROXY ? filterEnvForProxies(process.env) : undefined,
// This normalized from both --use-env-proxy and NODE_USE_ENV_PROXY settings.
proxyEnv: getOptionValue('--use-env-proxy') ? filterEnvForProxies(process.env) : undefined,
});

/**
Expand Down
29 changes: 17 additions & 12 deletions lib/internal/process/pre_execution.js
Original file line number Diff line number Diff line change
Expand Up @@ -166,19 +166,24 @@ function prepareExecution(options) {
}

function setupHttpProxy() {
if (process.env.NODE_USE_ENV_PROXY &&
(process.env.HTTP_PROXY || process.env.HTTPS_PROXY ||
process.env.http_proxy || process.env.https_proxy)) {
const { setGlobalDispatcher, EnvHttpProxyAgent } = require('internal/deps/undici/undici');
const envHttpProxyAgent = new EnvHttpProxyAgent();
setGlobalDispatcher(envHttpProxyAgent);
// For fetch, we need to set the global dispatcher from here.
// For http/https agents, we'll configure the global agent when they are
// actually created, in lib/_http_agent.js and lib/https.js.
// TODO(joyeecheung): This is currently guarded with NODE_USE_ENV_PROXY. Investigate whether
// it's possible to enable it by default without stepping on other existing libraries that
// sets the global dispatcher or monkey patches the global agent.
// This normalized from both --use-env-proxy and NODE_USE_ENV_PROXY settings.
if (!getOptionValue('--use-env-proxy')) {
return;
}
if (!process.env.HTTP_PROXY && !process.env.HTTPS_PROXY &&
!process.env.http_proxy && !process.env.https_proxy) {
return;
}

const { setGlobalDispatcher, EnvHttpProxyAgent } = require('internal/deps/undici/undici');
const envHttpProxyAgent = new EnvHttpProxyAgent();
setGlobalDispatcher(envHttpProxyAgent);
// For fetch, we need to set the global dispatcher from here.
// For http/https agents, we'll configure the global agent when they are
// actually created, in lib/_http_agent.js and lib/https.js.
// TODO(joyeecheung): This is currently guarded with NODE_USE_ENV_PROXY and --use-env-proxy.
// Investigate whether it's possible to enable it by default without stepping on other
// existing libraries that sets the global dispatcher or monkey patches the global agent.
}

function setupUserModules(forceDefaultLoader = false) {
Expand Down
8 changes: 8 additions & 0 deletions src/node_options.cc
Original file line number Diff line number Diff line change
Expand Up @@ -664,6 +664,12 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
"emit pending deprecation warnings",
&EnvironmentOptions::pending_deprecation,
kAllowedInEnvvar);
AddOption("--use-env-proxy",
"parse proxy settings from HTTP_PROXY/HTTPS_PROXY/NO_PROXY"
"environment variables and apply the setting in global HTTP/HTTPS "
"clients",
&EnvironmentOptions::use_env_proxy,
kAllowedInEnvvar);
AddOption("--preserve-symlinks",
"preserve symbolic links when resolving",
&EnvironmentOptions::preserve_symlinks,
Expand Down Expand Up @@ -1878,6 +1884,8 @@ void HandleEnvOptions(std::shared_ptr<EnvironmentOptions> env_options,
env_options->preserve_symlinks_main =
opt_getter("NODE_PRESERVE_SYMLINKS_MAIN") == "1";

env_options->use_env_proxy = opt_getter("NODE_USE_ENV_PROXY") == "1";

if (env_options->redirect_warnings.empty())
env_options->redirect_warnings = opt_getter("NODE_REDIRECT_WARNINGS");
}
Expand Down
1 change: 1 addition & 0 deletions src/node_options.h
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,7 @@ class EnvironmentOptions : public Options {
bool force_repl = false;

bool insecure_http_parser = false;
bool use_env_proxy = false;

bool tls_min_v1_0 = false;
bool tls_min_v1_1 = false;
Expand Down
2 changes: 0 additions & 2 deletions test/client-proxy/test-http-proxy-request.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,6 @@ if (!common.isWindows) {
REQUEST_URL: requestUrl,
http_proxy: `http://localhost:${proxy.address().port}`,
HTTP_PROXY: `http://localhost:${proxy2.address().port}`,
}, {
stdout: 'Hello world',
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These were copy-pasta from #58980 - runProxiedRequest didn't take a second argument. So removed these bogus expectations to make way for the actual new second argument.

});
assert.deepStrictEqual(logs, expectedLogs);
assert.strictEqual(stderr.trim(), '');
Expand Down
2 changes: 0 additions & 2 deletions test/client-proxy/test-https-proxy-request.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,6 @@ if (!common.isWindows) {
https_proxy: `http://localhost:${proxy.address().port}`,
HTTPS_PROXY: `http://localhost:${proxy2.address().port}`,
NODE_EXTRA_CA_CERTS: fixtures.path('keys', 'fake-startcom-root-cert.pem'),
}, {
stdout: 'Hello world',
});
assert.deepStrictEqual(logs, expectedLogs);
assert.strictEqual(stderr.trim(), '');
Expand Down
75 changes: 75 additions & 0 deletions test/client-proxy/test-use-env-proxy-cli-http.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// This tests that --use-env-proxy works the same as NODE_USE_ENV_PROXY=1
// for HTTP requests using the built-in http module and fetch API.

import * as common from '../common/index.mjs';
import assert from 'node:assert';
import http from 'node:http';
import { once } from 'events';
import { createProxyServer, runProxiedRequest, checkProxiedFetch } from '../common/proxy-server.js';

// Start a minimal proxy server.
const { proxy, logs } = createProxyServer();
proxy.listen(0);
await once(proxy, 'listening');

delete process.env.NODE_USE_ENV_PROXY; // Ensure the environment variable is not set.

// Start a HTTP server to process the final request.
const server = http.createServer(common.mustCall((req, res) => {
res.end('Hello world');
}, 2));
server.on('error', common.mustNotCall((err) => { console.error('Server error', err); }));
server.listen(0);
await once(server, 'listening');

const serverHost = `localhost:${server.address().port}`;
const requestUrl = `http://${serverHost}/test`;

// Tests --use-env-proxy works with http builtins.
{
const { code, signal, stderr, stdout } = await runProxiedRequest({
REQUEST_URL: requestUrl,
HTTP_PROXY: `http://localhost:${proxy.address().port}`,
}, ['--use-env-proxy']);
assert.strictEqual(stderr.trim(), '');
assert.match(stdout, /Hello world/);
assert.strictEqual(code, 0);
assert.strictEqual(signal, null);
assert.deepStrictEqual(logs, [{
method: 'GET',
url: requestUrl,
headers: {
'connection': 'keep-alive',
'proxy-connection': 'keep-alive',
'host': serverHost,
},
}]);
}

// Tests --use-env-proxy works with fetch and http.
{
logs.splice(0, logs.length);
await checkProxiedFetch({
FETCH_URL: requestUrl,
HTTP_PROXY: `http://localhost:${proxy.address().port}`,
}, {
stdout: 'Hello world',
}, ['--use-env-proxy']);

// FIXME(undici:4083): undici currently always tunnels the request over
// CONNECT if proxyTunnel is not explicitly set to false, but what we
// need is for it to be automatically false for HTTP requests to be
// consistent with curl.
assert.deepStrictEqual(logs, [{
method: 'CONNECT',
url: serverHost,
headers: {
'connection': 'close',
'proxy-connection': 'keep-alive',
'host': serverHost,
},
}]);
}

server.close();
proxy.close();
81 changes: 81 additions & 0 deletions test/client-proxy/test-use-env-proxy-cli-https.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// This tests that --use-env-proxy works the same as NODE_USE_ENV_PROXY=1
// for HTTPS requests using the built-in https module and fetch API.

import * as common from '../common/index.mjs';
import fixtures from '../common/fixtures.js';
import assert from 'node:assert';
import { once } from 'events';
import { createProxyServer, runProxiedRequest, checkProxiedFetch } from '../common/proxy-server.js';

if (!common.hasCrypto) {
common.skip('missing crypto');
}

// https must be dynamically imported so that builds without crypto support
// can skip it.
const { default: https } = await import('node:https');

// Start a minimal proxy server.
const { proxy, logs } = createProxyServer();
proxy.listen(0);
await once(proxy, 'listening');

delete process.env.NODE_USE_ENV_PROXY; // Ensure the environment variable is not set.
// Start a HTTPS server to process the final request.
const server = https.createServer({
cert: fixtures.readKey('agent8-cert.pem'),
key: fixtures.readKey('agent8-key.pem'),
}, common.mustCall((req, res) => {
res.end('Hello world');
}, 2));
server.on('error', common.mustNotCall((err) => { console.error('Server error', err); }));
server.listen(0);
await once(server, 'listening');

const serverHost = `localhost:${server.address().port}`;
const requestUrl = `https://${serverHost}/test`;

// Tests --use-env-proxy works with https builtins.
{
const { code, signal, stderr, stdout } = await runProxiedRequest({
REQUEST_URL: requestUrl,
HTTPS_PROXY: `http://localhost:${proxy.address().port}`,
NODE_EXTRA_CA_CERTS: fixtures.path('keys', 'fake-startcom-root-cert.pem'),
}, ['--use-env-proxy']);
assert.strictEqual(stderr.trim(), '');
assert.match(stdout, /Hello world/);
assert.strictEqual(code, 0);
assert.strictEqual(signal, null);
assert.deepStrictEqual(logs, [{
method: 'CONNECT',
url: serverHost,
headers: {
'proxy-connection': 'keep-alive',
'host': serverHost,
},
}]);
}

// Tests --use-env-proxy works with fetch and https.
{
logs.splice(0, logs.length);
await checkProxiedFetch({
FETCH_URL: `https://${serverHost}/test`,
HTTPS_PROXY: `http://localhost:${proxy.address().port}`,
NODE_EXTRA_CA_CERTS: fixtures.path('keys', 'fake-startcom-root-cert.pem'),
}, {
stdout: 'Hello world',
}, ['--use-env-proxy']);
assert.deepStrictEqual(logs, [{
method: 'CONNECT',
url: serverHost,
headers: {
'connection': 'close',
'proxy-connection': 'keep-alive',
'host': serverHost,
},
}]);
}

server.close();
proxy.close();
Loading
Loading