A proxy environment variable can be present while an application connects directly to its destination. In our tests, the difference depended on the client, the spelling of the setting, the URL scheme, and whether proxy support had been enabled.
We ran 1,080 controlled local trials, followed by 80 public HTTPS attempts, using curl, Python’s urllib, Python Requests, and Node’s native fetch. The local experiment covered 360 combinations repeated three times. Node was tested with its default configuration and with environment proxy support explicitly enabled.
The failures themselves produced a reassuring result within this test: among 420 failure trials matched to healthy baselines that used the proxy for every request leg, none delivered a direct request to our destination. Direct traffic appeared in configurations where the proxy was bypassed or never engaged.
These are application routing findings. We did not test a paid proxy provider, discover a new vulnerability, or measure browser anonymity. For background, read our guide to proxy servers and the difference between a VPN and a proxy.
One Debian PC, one measurement session. Four client implementations; Node fetch in two modes. No paid provider or browser anonymity test.
What we measured
We configured a small proxy and two destination servers on one Debian PC. One destination served HTTP; the other served HTTPS. A third path redirected from the HTTP destination to the HTTPS destination.
The destination recorded whether each synthetic request arrived from the proxy’s outgoing connection or directly from the client. For HTTPS, the proxy forwarded an encrypted CONNECT tunnel. It did not decrypt TLS. A temporary certificate was trusted only by the test processes, and a negative control confirmed that an unrelated certificate authority was rejected.
Each trial launched a fresh client process with a controlled environment. We removed inherited proxy settings, disabled curl’s user configuration file, and used a temporary home directory. This prevented an existing shell setting or user configuration from silently changing the experiment.
The installed versions were curl 8.14.1, Python 3.13.5, Requests 2.32.3, and Node 25.2.1. Our evidence records curl’s Debian patch version and complete runtime details.
We tested four configurations:
- Lowercase
http_proxyandhttps_proxytogether. - Uppercase
HTTP_PROXYandHTTPS_PROXYtogether. - Lowercase
http_proxyalone. - Both lowercase settings plus a deliberate
NO_PROXYexception for the destination.
Each configuration faced a healthy proxy and five failure conditions: connection refusal, HTTP 407 authentication required, HTTP 503 unavailable, a TCP reset, and a connection that accepted the request but stalled.
Finding 1: default Node fetch did not engage the environment proxy
With both lowercase proxy variables present, default Node fetch connected directly for HTTP, HTTPS, and the redirect path. Enabling NODE_USE_ENV_PROXY=1 changed all three paths to use the proxy.
We reproduced that distinction against a public HTTPS destination. Default fetch returned a valid response without contacting our researcher-operated proxy. The enabled mode established a CONNECT tunnel through it.
This behavior follows the tested runtime’s configuration model. Node documents the environment proxy option; the result should not be described as a Node security vulnerability. It demonstrates why checking that an environment variable exists is insufficient evidence that a particular application uses it. Node 25.2.1 documentation
Finding 2: uppercase settings did not mean the same thing to every client
In the local HTTP tests, curl connected directly when given uppercase HTTP_PROXY, while urllib, Requests, and proxy-enabled Node used the proxy. For HTTPS, curl used uppercase HTTPS_PROXY.
curl deliberately treats http_proxy differently: it accepts the HTTP variable in lowercase to avoid a CGI-related security problem. The measured behavior is documented and intentional. curl’s proxy environment documentation
That produced a distinctive local redirect route. With uppercase settings, curl sent the initial HTTP request directly and the redirected HTTPS request through the proxy. A test that checked only the final HTTPS response would miss the first leg.
We did not repeat this HTTP redirect scenario against a public destination, so its measured scope remains the controlled local fixture.
The complete working-proxy route matrix
Each cell lists HTTP, HTTPS, then HTTP → HTTPS. All three local repetitions agreed. These are routes, not product scores.
| Client mode | Lowercase HTTP + HTTPS | Uppercase HTTP + HTTPS | http_proxy only | Explicit NO_PROXY exception |
|---|---|---|---|---|
| curl | HTTPProxy HTTPSProxy RedirectProxy → Proxy | HTTPDirect HTTPSProxy RedirectDirect → Proxy | HTTPProxy HTTPSDirect RedirectProxy → Direct | HTTPDirect HTTPSDirect RedirectDirect → Direct |
| Python urllib | HTTPProxy HTTPSProxy RedirectProxy → Proxy | HTTPProxy HTTPSProxy RedirectProxy → Proxy | HTTPProxy HTTPSDirect RedirectProxy → Direct | HTTPDirect HTTPSDirect RedirectDirect → Direct |
| Python Requests | HTTPProxy HTTPSProxy RedirectProxy → Proxy | HTTPProxy HTTPSProxy RedirectProxy → Proxy | HTTPProxy HTTPSDirect RedirectProxy → Direct | HTTPDirect HTTPSDirect RedirectDirect → Direct |
| Node fetch · default | HTTPDirect HTTPSDirect RedirectDirect → Direct | HTTPDirect HTTPSDirect RedirectDirect → Direct | HTTPDirect HTTPSDirect RedirectDirect → Direct | HTTPDirect HTTPSDirect RedirectDirect → Direct |
| Node fetch · proxy enabled | HTTPProxy HTTPSProxy RedirectProxy → Proxy | HTTPProxy HTTPSProxy RedirectProxy → Proxy | HTTPProxy HTTPSProxy RedirectProxy → Proxy | HTTPDirect HTTPSDirect RedirectDirect → Direct |
Finding 3: an HTTP-to-HTTPS upgrade could change the route
When only lowercase http_proxy was supplied, curl, urllib, and Requests used the proxy for the initial local HTTP request, then connected directly for the redirected HTTPS request.
Proxy-enabled Node behaved differently: it used the HTTP proxy setting for both HTTP and HTTPS in this configuration. Default Node fetch connected directly throughout.
| Client configuration | HTTP | HTTPS | HTTP → HTTPS |
|---|---|---|---|
curl, http_proxy only | Proxy | Direct | Proxy → Direct |
urllib, http_proxy only | Proxy | Direct | Proxy → Direct |
Requests, http_proxy only | Proxy | Direct | Proxy → Direct |
Node fetch, default, http_proxy only | Direct | Direct | Direct → Direct |
Node fetch, proxy mode enabled, http_proxy only | Proxy | Proxy | Proxy → Proxy |
All three repetitions agreed for each local combination. Public HTTPS checks reproduced the HTTPS column: curl, urllib, Requests, and default Node fetched directly; enabled Node used the test proxy.
The practical implication is specific: validating one HTTP request does not establish how the same configuration routes HTTPS. Requests and urllib each document their proxy configuration mechanisms; users need to apply the configuration supported by the client they actually run. Requests documentation, Python urllib documentation

Finding 4: an engaged proxy did not fall back to direct traffic in our failure tests
We first identified the client/configuration/path combinations that used the proxy for every leg in all three healthy baselines. There were 28 such combinations.
Across their five failure conditions and three repetitions, we observed 420 matched failure trials. None reached the controlled destination by any route.
A proxy-generated HTTP 407 or 503 response did not count as destination receipt. Some clients can return such a response to an application without a transport-level error, so an exit code alone would be an inadequate routing test.
This result is bounded. It applies to these versions, settings, fixtures, and failure conditions. Our HTTP versus SOCKS proxy guide explains why a different protocol requires its own testing. We did not test PAC fallback rules, browser extensions, SOCKS, mid-transfer failures, connection reuse, or every possible retry policy.
Five failures, 84 matched trials each
Only combinations whose healthy baselines used the proxy for every leg are included: 28 combinations × 3 repetitions per failure.
What the public checks add
Local addresses can receive special treatment from proxy implementations. We therefore repeated the HTTPS configuration comparisons using api.ipify.org, a public IP-echo endpoint.
The public phase covered five client modes, four configurations, two proxy states, and two repetitions: 80 attempts. There were 62 valid responses—44 direct and 18 through the researcher proxy. The remaining 18 attempts returned no valid response in refused-proxy configurations whose healthy counterparts had used the proxy.
These totals describe deliberately selected configurations. They are not a population estimate that a percentage of proxy users leaks traffic. Both repetitions agreed on the observed route or failed-response outcome for each public configuration.
Our temporary forwarding proxy ran on the same PC as the clients. It therefore shared their Internet egress. Its CONNECT records establish that a request used the configured proxy path; they do not demonstrate that a remote paid proxy concealed the PC’s public IP. Raw public IP responses were handled in memory and saved only as opaque, salted comparison labels.
Where the public HTTPS attempts went
- 44 Direct response
- 18 Research-proxy response
- 18 No valid response
Public outcomes by client mode
16 attempts per mode; healthy and refused proxy states combined.
A better way to check a proxy configuration
For the application and version you use, establish a working baseline first. Confirm the proxy receives the request. Test each relevant URL scheme and redirect leg. Then make the proxy unavailable and check whether the destination still receives a request.
Separate three outcomes: a client that never engaged the proxy, an intentional bypass rule, and a client that changes from a proven proxy route to a direct route after failure. Combining those outcomes into one “leak rate” would hide the cause.
Our study’s central finding is a configuration gap: a proxy setting can look complete while leaving a particular client or request scheme on a direct route. The reproducible route evidence makes that gap visible.
Route verification is only one layer. Our proxy-password transport experiment separates destination HTTPS from the connection to the proxy, while our reporting-service access study distinguishes proxy tunnel refusals from destination responses. For practical configuration steps, use the Windows and Mac proxy setup guide.
Evidence, exclusions, and disclosure
The accompanying package contains the scripts, exact main-run source snapshots, individual trial records, server events, aggregates, controls, and SHA-256 manifests. The 360-trial exploratory fixture pilot is excluded from the main totals.
An earlier Honeygain supplier setup attempt is a separate experiment. It authenticated but received “Network Overused”; it did not establish customer relay traffic or consent-withdrawal behavior, and it contributes no observations to this routing matrix.
Our research is about application routing, rather than the separate browser and network mechanisms described in our WebRTC and IPv6 leak guide. No proxy vendor supplied a paid endpoint or sponsored this measurement session. No café customer traffic was captured. All services were temporary, no recurring task was created, and neither the router nor the PC’s VPN configuration was changed.
Download the evidence and reproduce the result
Individual trials, controls, exact source snapshots, aggregates and SHA-256 manifests are included. The exploratory pilot and Honeygain data are excluded from the published routing package.
Frequently Asked Questions
Sources & Research Notes
Affiliate disclosure: This article contains Proton VPN affiliate links. FreePolicyTools may earn a commission if you purchase through them. These offers are separate from the research findings and do not constitute a comparative product test or change the study’s methods, results or limitations.