{"id":1107,"date":"2026-10-13T19:17:27","date_gmt":"2026-10-13T16:17:27","guid":{"rendered":"https:\/\/mexela.com\/blog\/rust-reqwest-proxy\/"},"modified":"2026-10-13T20:19:37","modified_gmt":"2026-10-13T17:19:37","slug":"rust-reqwest-proxy","status":"publish","type":"post","link":"https:\/\/mexela.com\/blog\/rust-reqwest-proxy\/","title":{"rendered":"Rust reqwest Proxy Setup: Authentication, HTTPS CONNECT and TLS"},"content":{"rendered":"<p class=\"mexela-answer\">To route a Rust reqwest request through a proxy, add <code>Proxy::all(endpoint)<\/code> to a client builder, apply proxy credentials with <code>Proxy::basic_auth()<\/code>, keep TLS validation enabled, and check the received HTTP status separately from transport errors. Make a direct baseline with <code>ClientBuilder::no_proxy()<\/code> before diagnosing the selected proxy route.<\/p>\n<p><strong>Reviewed and locally tested: October 13, 2026.<\/strong> The executed runtime was Rust 1.99.0 and reqwest 0.13.5 on Ubuntu 24.04 inside WSL2, using the Linux <code>x86_64-unknown-linux-gnu<\/code> target. These observations do not certify native Windows, WebAssembly, older reqwest releases, or every TLS\/backend combination.<\/p>\n<h2 id=\"runtime\">Choose the client API and Cargo features<\/h2>\n<pre><code class=\"language-bash\">rustc --version\ncargo --version<\/code><\/pre>\n<p>This guide uses <code>reqwest::blocking<\/code> for a small diagnostic program. Do not call this blocking API from within an async runtime. Existing Tokio applications should use the async <code>reqwest::Client<\/code> and <code>.send().await<\/code>; those application paths were not exercised by this lab. Read the <a href=\"https:\/\/docs.rs\/reqwest\/0.13.5\/reqwest\/blocking\/index.html\">official blocking API guidance<\/a>.<\/p>\n<p>The examples use the following exact reqwest version and feature selection. The supplied <code>Cargo.lock<\/code> also records the resolved transitive versions; retain it for reproducible application builds and review dependencies before changing that graph.<\/p>\n<pre><code class=\"language-toml\">[dependencies]\nreqwest = { version = \"=0.13.5\", default-features = false, features = [\"blocking\", \"rustls\", \"socks\"] }<\/code><\/pre>\n<p>The <code>rustls<\/code> feature enables TLS and <code>socks<\/code> 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 <a href=\"https:\/\/docs.rs\/reqwest\/0.13.5\/reqwest\/#optional-features\">official feature list<\/a> and <a href=\"https:\/\/rust-lang.github.io\/rustup\/installation\/index.html\">Rust installation instructions<\/a> for your platform.<\/p>\n<h2 id=\"example\">Run the actual tested Rust example<\/h2>\n<p>The <a href=\"\/blog\/wp-content\/uploads\/2026\/10\/mexela-rust-proxy-examples-20261013-v1.zip\">Rust proxy examples ZIP<\/a> contains <code>Cargo.toml<\/code>, <code>Cargo.lock<\/code>, the starter, and this exact <code>src\/bin\/article-example.rs<\/code>. Set an approved <code>CHECK_URL<\/code>, credential-free <code>PROXY_ENDPOINT<\/code>, and separate <code>PROXY_USER<\/code>\/<code>PROXY_PASSWORD<\/code> through your existing secret-management process. For example, the endpoint format is <code>http:\/\/proxy.example:8080<\/code>; do not put credentials in the URL or shell history.<\/p>\n<pre><code class=\"language-bash\">cargo build --locked\ncargo run --locked --bin article-example<\/code><\/pre>\n<pre><code class=\"language-rust\">use reqwest::{blocking::Client, redirect::Policy, Certificate, Proxy, Url};\nuse std::{env, error::Error, time::Duration};\n\nfn run() -&gt; Result&lt;u16, Box&lt;dyn Error&gt;&gt; {\n    let destination = Url::parse(&amp;env::var(&quot;CHECK_URL&quot;)?)?;\n    if ![&quot;http&quot;, &quot;https&quot;].contains(&amp;destination.scheme())\n        || !destination.username().is_empty() || destination.password().is_some() {\n        return Err(&quot;invalid destination&quot;.into());\n    }\n    let mut builder = Client::builder()\n        .no_proxy()\n        .connect_timeout(Duration::from_secs(2))\n        .timeout(Duration::from_secs(5))\n        .redirect(Policy::none());\n    if let Ok(endpoint) = env::var(&quot;PROXY_ENDPOINT&quot;) {\n        let endpoint = Url::parse(&amp;endpoint)?;\n        if ![&quot;http&quot;, &quot;https&quot;, &quot;socks5&quot;, &quot;socks5h&quot;].contains(&amp;endpoint.scheme())\n            || !endpoint.username().is_empty() || endpoint.password().is_some() {\n            return Err(&quot;invalid proxy endpoint&quot;.into());\n        }\n        let proxy = Proxy::all(endpoint)?\n            .basic_auth(&amp;env::var(&quot;PROXY_USER&quot;)?, &amp;env::var(&quot;PROXY_PASSWORD&quot;)?);\n        builder = builder.proxy(proxy);\n    }\n    if let Ok(path) = env::var(&quot;CA_FILE&quot;) {\n        let pem = std::fs::read(path)?;\n        builder = builder.tls_certs_merge([Certificate::from_pem(&amp;pem)?]);\n    }\n    let client = builder.build()?;\n    Ok(client.get(destination).send()?.status().as_u16())\n}\n\nfn main() {\n    match run() {\n        Ok(status) =&gt; println!(&quot;http_status={status}&quot;),\n        Err(_) =&gt; {\n            eprintln!(&quot;Request failed; inspect private diagnostics safely.&quot;);\n            std::process::exit(1);\n        }\n    }\n}<\/code><\/pre>\n<p>This example returns a numeric HTTP status after response headers and logs a fixed message if the operation fails. Interpret that status: <code>407<\/code> is not an accepted request simply because <code>send()<\/code> 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.<\/p>\n<h2 id=\"routing\">Separate explicit routing from inherited environment settings<\/h2>\n<p>The displayed code starts with <code>.no_proxy()<\/code>, disabling inherited proxies for the direct baseline. If <code>PROXY_ENDPOINT<\/code> exists, it adds one explicit <code>Proxy::all()<\/code> rule for HTTP and HTTPS destinations. This is a per-client configuration; other clients and browser traffic require their own settings and route checks.<\/p>\n<p>We also executed a separate default client that inherited <code>HTTP_PROXY<\/code>. Another client with <code>.no_proxy()<\/code> bypassed that inherited endpoint. An explicit proxy rule still reached the proxy when inherited <code>NO_PROXY=*<\/code> was set. Set intentional bypass rules with <code>Proxy::no_proxy()<\/code> when your application needs them; do not assume an environment bypass controls every explicit rule. See <a href=\"https:\/\/docs.rs\/reqwest\/0.13.5\/reqwest\/struct.Proxy.html\">the official proxy API<\/a> and <a href=\"\/blog\/http-proxy-https-proxy-no-proxy\/\">HTTP_PROXY, HTTPS_PROXY and NO_PROXY<\/a>.<\/p>\n<p>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&#8217;s public exit, country, speed, anonymity, or DNS privacy.<\/p>\n<h2 id=\"credentials\">Keep proxy and destination credentials separate<\/h2>\n<p><code>Proxy::basic_auth()<\/code> configures the proxy credential layer. By contrast, <code>client.get(url).basic_auth(destination_user, Some(destination_password))<\/code> 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.<\/p>\n<p>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&#8217;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 <a href=\"\/blog\/proxy-authentication-username-password-vs-ip-auth\/\">proxy authentication<\/a>.<\/p>\n<h2 id=\"tls-connect\">Keep TLS strict through HTTPS CONNECT<\/h2>\n<p>An <code>http:\/\/<\/code> 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.<\/p>\n<p>After adding the fixture&#8217;s approved CA with <code>Certificate::from_pem()<\/code> and <code>ClientBuilder::tls_certs_merge()<\/code>, the same destination succeeded with certificate and hostname validation still enabled. <code>CA_FILE<\/code> 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&#8217;s certificate configuration. See <a href=\"https:\/\/docs.rs\/reqwest\/0.13.5\/reqwest\/blocking\/struct.ClientBuilder.html#method.tls_certs_merge\">reqwest&#8217;s client trust configuration<\/a> and <a href=\"\/blog\/http-https-socks5-proxies\/\">HTTP, HTTPS and SOCKS5 differences<\/a>.<\/p>\n<h2 id=\"socks\">SOCKS5 support depends on the build<\/h2>\n<p>With the <code>socks<\/code> feature enabled, the executed build accepted an authenticated <code>socks5h:\/\/<\/code> 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&#8217;s authentication method.<\/p>\n<h2 id=\"results\">What the local lab actually observed<\/h2>\n<table>\n<thead>\n<tr>\n<th>Executed check<\/th>\n<th>Observation<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Direct HTTP\/HTTPS and authenticated HTTP forwarding<\/td>\n<td>200; destination distinguished direct and proxy routes<\/td>\n<\/tr>\n<tr>\n<td>Environment inheritance and explicit direct\/proxy clients<\/td>\n<td>Observed routes matched the selected client configuration<\/td>\n<\/tr>\n<tr>\n<td>Wrong or absent HTTP proxy credentials<\/td>\n<td>407 response; no destination request<\/td>\n<\/tr>\n<tr>\n<td>Wrong or absent CONNECT credentials<\/td>\n<td>Fixture tunnel 407; reqwest transport error, no destination status<\/td>\n<\/tr>\n<tr>\n<td>Untrusted certificate, approved CA, and hostname mismatch<\/td>\n<td>Strict TLS failed, approved-CA CONNECT succeeded, and a mismatched hostname still failed<\/td>\n<\/tr>\n<tr>\n<td>SOCKS5 and separate destination credentials<\/td>\n<td>200 on selected routes; no proxy credential header at destination<\/td>\n<\/tr>\n<tr>\n<td>Redirect and stalled response<\/td>\n<td>302 returned without following; 350 ms timeout measured 401 ms including helper\/client setup<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>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.<\/p>\n<h2 id=\"diagnostics\">Read transport errors and statuses at the correct layer<\/h2>\n<p>Use <code>Error::is_timeout()<\/code> and <code>Error::is_connect()<\/code> 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 <a href=\"https:\/\/docs.rs\/reqwest\/0.13.5\/reqwest\/struct.Error.html\">official Error reference<\/a> 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.<\/p>\n<p>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&#8217;s idempotency. Reuse a configured client for an application&#8217;s normal workload instead of treating these single-request lab timings as a throughput benchmark.<\/p>\n<h2 id=\"faq\">Frequently asked questions<\/h2>\n<h3>Does reqwest automatically use HTTP_PROXY?<\/h3>\n<p>A default native client can inherit proxy environment settings. Our Linux lab observed HTTP_PROXY routing. <code>ClientBuilder::no_proxy()<\/code> disables that inheritance for the configured client; explicit proxy rules and intentionally supplied bypass rules should be reviewed separately.<\/p>\n<h3>Should I disable certificate checks to fix CONNECT?<\/h3>\n<p>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.<\/p>\n<h3>Can I use this blocking code inside Tokio?<\/h3>\n<p>Use reqwest&#8217;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.<\/p>\n<p>Continue with <a href=\"\/blog\/proxy-setup-developer-guides\/\">Developer Guides<\/a>, <a href=\"\/blog\/proxy-testing-methodology\/\">the testing methodology<\/a>, <a href=\"\/blog\/test-if-your-proxy-is-working\/\">the route verification checklist<\/a>, and <a href=\"\/blog\/wsl2-proxy-setup\/\">WSL2 proxy setup<\/a>.<\/p>\n<h2 id=\"versioned-source-repository\">Versioned Rust source files<\/h2>\n<p>Use the <a href=\"\/blog\/proxy-code-examples-repository\/#rust\">Rust folder in the public source repository<\/a> 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.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Configure Rust reqwest proxy routing, separate credentials, strict TLS and bounded diagnostics with executed HTTP, CONNECT and SOCKS5 examples.<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[189],"tags":[],"_links":{"self":[{"href":"https:\/\/mexela.com\/blog\/wp-json\/wp\/v2\/posts\/1107"}],"collection":[{"href":"https:\/\/mexela.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/mexela.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/mexela.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/mexela.com\/blog\/wp-json\/wp\/v2\/comments?post=1107"}],"version-history":[{"count":0,"href":"https:\/\/mexela.com\/blog\/wp-json\/wp\/v2\/posts\/1107\/revisions"}],"wp:attachment":[{"href":"https:\/\/mexela.com\/blog\/wp-json\/wp\/v2\/media?parent=1107"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/mexela.com\/blog\/wp-json\/wp\/v2\/categories?post=1107"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/mexela.com\/blog\/wp-json\/wp\/v2\/tags?post=1107"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}