To route a Rust reqwest request through a proxy, add Proxy::all(endpoint) to a client builder, apply proxy credentials with Proxy::basic_auth(), keep TLS validation enabled, and check the received HTTP status separately from transport errors. Make a direct baseline with ClientBuilder::no_proxy() before diagnosing the selected proxy route.
Reviewed and locally tested: October 13, 2026. The executed runtime was Rust 1.99.0 and reqwest 0.13.5 on Ubuntu 24.04 inside WSL2, using the Linux x86_64-unknown-linux-gnu target. These observations do not certify native Windows, WebAssembly, older reqwest releases, or every TLS/backend combination.
Choose the client API and Cargo features
rustc --version
cargo --version
This guide uses reqwest::blocking for a small diagnostic program. Do not call this blocking API from within an async runtime. Existing Tokio applications should use the async reqwest::Client and .send().await; those application paths were not exercised by this lab. Read the official blocking API guidance.
The examples use the following exact reqwest version and feature selection. The supplied Cargo.lock also records the resolved transitive versions; retain it for reproducible application builds and review dependencies before changing that graph.
[dependencies]
reqwest = { version = "=0.13.5", default-features = false, features = ["blocking", "rustls", "socks"] }
The rustls feature enables TLS and socks enables SOCKS5 support. Disabling default features here also omits automatic Windows/macOS system-proxy integration; the loopback lab still tested environment proxy inheritance separately. This feature selection is a tested example, not a requirement for every application. Check the official feature list and Rust installation instructions for your platform.
Run the actual tested Rust example
The Rust proxy examples ZIP contains Cargo.toml, Cargo.lock, the starter, and this exact src/bin/article-example.rs. Set an approved CHECK_URL, credential-free PROXY_ENDPOINT, and separate PROXY_USER/PROXY_PASSWORD through your existing secret-management process. For example, the endpoint format is http://proxy.example:8080; do not put credentials in the URL or shell history.
cargo build --locked
cargo run --locked --bin article-example
use reqwest::{blocking::Client, redirect::Policy, Certificate, Proxy, Url};
use std::{env, error::Error, time::Duration};
fn run() -> Result<u16, Box<dyn Error>> {
let destination = Url::parse(&env::var("CHECK_URL")?)?;
if !["http", "https"].contains(&destination.scheme())
|| !destination.username().is_empty() || destination.password().is_some() {
return Err("invalid destination".into());
}
let mut builder = Client::builder()
.no_proxy()
.connect_timeout(Duration::from_secs(2))
.timeout(Duration::from_secs(5))
.redirect(Policy::none());
if let Ok(endpoint) = env::var("PROXY_ENDPOINT") {
let endpoint = Url::parse(&endpoint)?;
if !["http", "https", "socks5", "socks5h"].contains(&endpoint.scheme())
|| !endpoint.username().is_empty() || endpoint.password().is_some() {
return Err("invalid proxy endpoint".into());
}
let proxy = Proxy::all(endpoint)?
.basic_auth(&env::var("PROXY_USER")?, &env::var("PROXY_PASSWORD")?);
builder = builder.proxy(proxy);
}
if let Ok(path) = env::var("CA_FILE") {
let pem = std::fs::read(path)?;
builder = builder.tls_certs_merge([Certificate::from_pem(&pem)?]);
}
let client = builder.build()?;
Ok(client.get(destination).send()?.status().as_u16())
}
fn main() {
match run() {
Ok(status) => println!("http_status={status}"),
Err(_) => {
eprintln!("Request failed; inspect private diagnostics safely.");
std::process::exit(1);
}
}
}
This example returns a numeric HTTP status after response headers and logs a fixed message if the operation fails. Interpret that status: 407 is not an accepted request simply because send() returned a response. The separate diagnostic starter reads at most 64 KiB of response data in memory and outputs selected JSON flags without raw bodies, URLs, headers, or errors.
Separate explicit routing from inherited environment settings
The displayed code starts with .no_proxy(), disabling inherited proxies for the direct baseline. If PROXY_ENDPOINT exists, it adds one explicit Proxy::all() rule for HTTP and HTTPS destinations. This is a per-client configuration; other clients and browser traffic require their own settings and route checks.
We also executed a separate default client that inherited HTTP_PROXY. Another client with .no_proxy() bypassed that inherited endpoint. An explicit proxy rule still reached the proxy when inherited NO_PROXY=* was set. Set intentional bypass rules with Proxy::no_proxy() when your application needs them; do not assume an environment bypass controls every explicit rule. See the official proxy API and HTTP_PROXY, HTTPS_PROXY and NO_PROXY.
A status 200 alone does not prove which route handled the request. Our destination correlated actual accepted sockets with proxy-created upstream sockets. That establishes the loopback route used in these cases; it does not establish a commercial proxy’s public exit, country, speed, anonymity, or DNS privacy.
Keep proxy and destination credentials separate
Proxy::basic_auth() configures the proxy credential layer. By contrast, client.get(url).basic_auth(destination_user, Some(destination_password)) belongs to the destination request. The lab used different random credential pairs and checked destination authentication inside CONNECT while confirming that the destination never received a Proxy-Authorization header.
An HTTP proxy connection does not encrypt Basic proxy authentication by itself, even when the destination is HTTPS through CONNECT. Choose an approved endpoint transport and network path. Wrong or absent HTTP proxy credentials produced a received 407 in our fixture. Wrong or absent CONNECT credentials failed before a destination request; the fixture recorded the tunnel’s 407, while reqwest returned a transport error with no destination HTTP status. Do not invent a destination 407 when no destination response was received. Continue with proxy authentication.
Keep TLS strict through HTTPS CONNECT
An http:// proxy can tunnel an HTTPS destination with CONNECT. The destination scheme and the proxy transport are separate settings. Our authenticated proxy accepted CONNECT, but strict default certificate validation then rejected the disposable untrusted certificate; no destination HTTP request arrived.
After adding the fixture’s approved CA with Certificate::from_pem() and ClientBuilder::tls_certs_merge(), the same destination succeeded with certificate and hostname validation still enabled. CA_FILE is scoped to the client in this example. Obtain a real CA from your trusted administration process rather than trusting an arbitrary certificate from a failed request. We did not test TLS to an HTTPS proxy or that proxy’s certificate configuration. See reqwest’s client trust configuration and HTTP, HTTPS and SOCKS5 differences.
SOCKS5 support depends on the build
With the socks feature enabled, the executed build accepted an authenticated socks5h:// endpoint with the same separate credential API and strict HTTPS validation. Our destination was an IP literal. That checks authenticated tunneling and the observed route; it does not test hostname resolution, DNS leakage, or every SOCKS server’s authentication method.
What the local lab actually observed
| Executed check | Observation |
|---|---|
| Direct HTTP/HTTPS and authenticated HTTP forwarding | 200; destination distinguished direct and proxy routes |
| Environment inheritance and explicit direct/proxy clients | Observed routes matched the selected client configuration |
| Wrong or absent HTTP proxy credentials | 407 response; no destination request |
| Wrong or absent CONNECT credentials | Fixture tunnel 407; reqwest transport error, no destination status |
| Untrusted certificate, approved CA, and hostname mismatch | Strict TLS failed, approved-CA CONNECT succeeded, and a mismatched hostname still failed |
| SOCKS5 and separate destination credentials | 200 on selected routes; no proxy credential header at destination |
| Redirect and stalled response | 302 returned without following; 350 ms timeout measured 401 ms including helper/client setup |
The lab passed 23 checks against owned loopback sockets, including compiling and executing the exact Rust code displayed above. Random fixture credentials were not persisted in the article, examples, or report. Temporary certificate/private-key files were removed after execution. No production endpoint, public exit geography, commercial speed, uptime, anonymity or native Windows compatibility was measured.
Read transport errors and statuses at the correct layer
Use Error::is_timeout() and Error::is_connect() as selected diagnostic flags, then inspect the destination status only when a response exists. These flags do not uniquely identify every TLS or tunnel cause. The official Error reference also notes that an error can contain a full URL. Raw errors, headers, response bodies and environment dumps can reveal secrets; use controlled private diagnostics when deeper evidence is necessary.
The starter configures a 2 second connection limit and a 5 second request limit, reads a bounded body, and disables automatic redirects. Our 350 ms case tested a connected server that stalled before sending a response; it does not characterize all resolvers or operating systems. Retry only after understanding the failing layer and the request’s idempotency. Reuse a configured client for an application’s normal workload instead of treating these single-request lab timings as a throughput benchmark.
Frequently asked questions
Does reqwest automatically use HTTP_PROXY?
A default native client can inherit proxy environment settings. Our Linux lab observed HTTP_PROXY routing. ClientBuilder::no_proxy() disables that inheritance for the configured client; explicit proxy rules and intentionally supplied bypass rules should be reviewed separately.
Should I disable certificate checks to fix CONNECT?
No. First distinguish proxy authentication or tunnel failure from destination certificate validation. Use an approved CA only when your trust policy requires it, keeping certificate and hostname checks enabled.
Can I use this blocking code inside Tokio?
Use reqwest’s async client within an async application. This guide executed a standalone blocking program; its fixture evidence does not qualify your async application or browser-based WebAssembly runtime.
Continue with Developer Guides, the testing methodology, the route verification checklist, and WSL2 proxy setup.
Versioned Rust source files
Use the Rust folder in the public source repository for the example, setup instructions, exact dependency versions and a sanitized test summary. The repository is a dated snapshot of owned examples; its loopback checks do not certify a commercial endpoint or every application configuration.
