Proxy Configured, Traffic Direct: 1,080 Tests of Four Common HTTP Clients

Proxy routing research: a client can reach a destination through a proxy or along a separate direct path

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.

1,080Controlled local trials
80Public HTTPS attempts
420Matched local failure trials
0 / 420Direct receipts after engaged-proxy failure

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_proxy and https_proxy together.
  • Uppercase HTTP_PROXY and HTTPS_PROXY together.
  • Lowercase http_proxy alone.
  • Both lowercase settings plus a deliberate NO_PROXY exception 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 modeLowercase HTTP + HTTPSUppercase HTTP + HTTPShttp_proxy onlyExplicit 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
Source: main local run, healthy-proxy subset (180 trials). A deliberate NO_PROXY exception is expected bypass behavior.

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 configurationHTTPHTTPSHTTP → HTTPS
curl, http_proxy onlyProxyDirectProxy → Direct
urllib, http_proxy onlyProxyDirectProxy → Direct
Requests, http_proxy onlyProxyDirectProxy → Direct
Node fetch, default, http_proxy onlyDirectDirectDirect → Direct
Node fetch, proxy mode enabled, http_proxy onlyProxyProxyProxy → 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

Local HTTP-only proxy configuration: HTTP travels through the test proxy, followed by a direct HTTPS request in curl, urllib and Requests
Illustration derived from local route evidence, not a captured screenshot. The public phase confirmed HTTPS configuration behavior but did not repeat this redirect sequence.

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.

Bar height shows the number of tested trials, not protection strength. Zero origin receipt was observed in all 420 matched trials. Other failure modes remain untested.

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

80attempts
  • 44 Direct response
  • 18 Research-proxy response
  • 18 No valid response
Selected configurations include intentional bypasses and incomplete settings. This distribution is not a population leak rate.

Public outcomes by client mode

16 attempts per mode; healthy and refused proxy states combined.

curl8 / 4 / 4
Python urllib8 / 4 / 4
Python Requests8 / 4 / 4
Node fetch · default16 / 0 / 0
Node fetch · proxy enabled4 / 6 / 6
Counts are direct / proxied / no valid response. Failed responses remain inconclusive; they are not automatically privacy passes.

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.