The starting point
A proxy variable can be present while a request goes directly to its destination. Check the client, destination scheme and bypass rules before blaming the gateway.
Separate destination selection from the proxy connection
These variables are conventions interpreted by each client, not an operating-system switch that routes every application. A working curl command does not prove that another library inherited or honored the same settings.
The variable name usually selects the destination scheme. HTTPS_PROXY can contain an http:// proxy URL: the destination uses HTTPS, while the client first connects to an HTTP proxy and requests a CONNECT tunnel. The proxy URL's scheme describes the connection to the proxy itself.
- http_proxy / HTTP_PROXY: choose a proxy for an HTTP destination, subject to the client's case rules.
- https_proxy / HTTPS_PROXY: choose a proxy for an HTTPS destination.
- all_proxy / ALL_PROXY: a fallback in curl and Requests when no applicable destination-specific proxy is selected. Do not assume every client implements it.
- no_proxy / NO_PROXY: request direct connections for matching destinations when the client's environment proxy selection applies. This is a routing exception, not another proxy address.
Identify the client and version actually making the request
Local routing checks for this guide used curl 8.7.1, Requests 2.34.2 on Python 3.14.7, and Node.js 26.8.1. The Node.js 24.5.0 release notes separately document the introduction of environment proxy support for its default HTTP/HTTPS agents; fetch gained its opt-in earlier, in 24.0.0. Those historical versions were documentation-reviewed, not executed here.
- curl accepts lowercase http_proxy but deliberately ignores uppercase HTTP_PROXY because CGI environments can derive that name from an incoming request header. HTTPS_PROXY and ALL_PROXY are accepted. A protocol-specific variable takes priority over ALL_PROXY.
- Requests normally reads all four variable families, including uppercase names. Its Python proxy discovery prefers lowercase when both cases disagree; CGI environments with REQUEST_METHOD set ignore uppercase HTTP_PROXY. Environment values can override session.proxies, so a session dictionary alone is not an isolation boundary.
- Node's built-in environment support requires opt-in: start the process with NODE_USE_ENV_PROXY=1. The default HTTP/HTTPS agents and native fetch then use HTTP_PROXY, HTTPS_PROXY and NO_PROXY; lowercase takes precedence. ALL_PROXY alone is not a substitute in this built-in mode. Custom agents, dispatchers and third-party packages need their own configuration review.
Treat NO_PROXY as a route change
Start with a comma-separated list of exact authorized destination hosts, then verify which requests actually match. Do not paste full URLs or paths into a host list. A wildcard of * requests direct routing for every destination in the clients discussed here; it is unsuitable as a casual fix for a required proxy.
There is no portable matching grammar you can assume across these clients. For example, curl documents CIDR matching from 7.86.0; Node's built-in documentation lists hostnames, domain suffixes, address ranges and host:port entries. A domain entry, wildcard or subnet that works in one client needs a separate test in another. DNS aliases and IP literals also change the name being matched.
If an internal request unexpectedly uses the proxy, inspect the actual destination hostname and client-specific match rules. If an external request unexpectedly goes direct, inspect both cases of NO_PROXY and any explicit agent configuration. Change only the routing rule approved for that deployment.
Observe proxy selection without a real gateway
Save this as proxy_env_lab.py and run python3 proxy_env_lab.py with curl installed. It starts two HTTP marker servers on random loopback ports: one represents a direct destination and one represents the selected proxy. The proxy marker answers locally; it does not forward traffic or test CONNECT, TLS, authentication or an exit IP.
Each curl process receives only the supplied test environment. --disable is its first option so a personal curlrc cannot change the experiment. The expected results are proxy, direct, direct, proxy. This demonstrates that even an explicit --proxy remains subject to NO_PROXY unless the bypass list is explicitly cleared for this local diagnostic.
import shutil
import subprocess
import threading
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
curl = shutil.which("curl")
if not curl:
raise SystemExit("Install curl before running this local lab")
def marker(label):
class Handler(BaseHTTPRequestHandler):
def do_GET(self):
body = label.encode()
self.send_response(200)
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
def log_message(self, *_):
pass
server = ThreadingHTTPServer(("127.0.0.1", 0), Handler)
threading.Thread(target=server.serve_forever, daemon=True).start()
return server, "http://127.0.0.1:" + str(server.server_port)
origin, destination = marker("direct")
proxy_server, proxy = marker("proxy")
cases = [
("environment", {"http_proxy": proxy}, [], "proxy"),
("host bypass", {"http_proxy": proxy, "NO_PROXY": "127.0.0.1"}, [], "direct"),
("explicit + bypass", {"NO_PROXY": "127.0.0.1"}, ["--proxy", proxy], "direct"),
("explicit + cleared bypass", {"NO_PROXY": "127.0.0.1"},
["--proxy", proxy, "--noproxy", ""], "proxy"),
]
try:
for label, environment, options, expected in cases:
result = subprocess.run(
[curl, "--disable", "--silent", "--show-error", "--fail",
"--connect-timeout", "2", "--max-time", "3", *options, destination],
env=environment, capture_output=True, text=True, timeout=5,
)
if result.returncode or result.stdout != expected:
raise SystemExit("Local route check failed: " + label)
print(label + ": " + result.stdout)
finally:
for server in (origin, proxy_server):
server.shutdown()
server.server_close()Make the application baseline deliberate
For a required-proxy curl diagnostic, use an explicit --proxy and --noproxy '' together, as the existing curl setup guide demonstrates. For Requests, supply the proxies argument on the request; use a dedicated Session with trust_env=False when the baseline must ignore inherited proxy settings. That also stops environment-derived authentication and CA-bundle configuration, so supply an approved custom trust bundle explicitly when needed.
For Node, choose either reviewed environment opt-in or an explicit compatible agent/dispatcher. Do not assume a library's custom agent inherits the global setting. Confirm the exact runtime used by the worker or service, not only the version installed in an interactive terminal.
Once the local rule is understood, compare one authorized destination with one provider-documented gateway. Keep TLS verification enabled, use a deadline, and confirm the request path with controlled endpoint or proxy observations. A successful HTTP status by itself does not establish which route was taken.
Record decisions without recording secrets
Compare the environment of the process that makes the request with its launcher, container or service configuration. Record variable names and whether they are set, the client version, destination scheme and observed route. Avoid whole-environment dumps, shell tracing, verbose protocol logs and raw exception text: a proxy URL may contain a password.
Have an approved secret manager populate any required credentials privately in the process environment. Do not paste them into a command, commit an environment file or send raw configuration to support. Environment variables are a delivery mechanism, not a secret vault. This local lab uses no credentials and says nothing about ipvolt service availability.
From reading to doing
Before you ship
- Identify the actual client, runtime version and destination scheme.
- Check uppercase and lowercase variable names without logging their values.
- Verify NO_PROXY matching and the selected route on an authorized destination.
- Compare a controlled environment with an explicit configuration before adding retries.
Sources & further reading
Technical references used for this guide. Check the documentation for your installed version and your provider’s supported configuration.
- Everything curl: proxy environment variables and case rules
- curl manual: --disable, --proxy and --noproxy
- Requests: environment proxy configuration
- Python: proxy discovery and CGI handling
- Node.js 24.5.0: environment proxy support in HTTP clients
- Node.js HTTP documentation: built-in proxy and NO_PROXY rules