Integration · HTTP clients
How to use a proxy with Rust reqwest
In reqwest, a proxy is added on the ClientBuilder: .proxy(reqwest::Proxy::all("http://host:port")?.basic_auth(user, pass)). Proxy::all covers HTTP and HTTPS targets, and basic_auth sets the Proxy-Authorization header, which also goes on the CONNECT request for HTTPS sites. A Client holds one proxy configuration, so one client per session or country.
Reviewed 30 Oct 2026
http://USERNAME _pool-arcfast _country-de:PASSWORD@ proxy.arcproxies.io:8000The short answer
Proxy::all(url)?.basic_auth(user, pass); adding any proxy turns off reqwest's automatic system-proxy lookup.- Set
timeoutandconnect_timeout: reqwest has no overall timeout by default. - A refused tunnel is an error with
is_connect()true and "proxy authorization required" in its source chain. - SOCKS5 needs the
socksfeature; usesocks5h://for DNS at the proxy.
Setup
Set up Rust reqwest with a proxy, step by step
- 01
Add the dependencies
The
socksfeature is only needed for SOCKS5.Cargo.toml3 lines[dependencies]reqwest = { version = "0.13", features = ["socks"] }tokio = { version = "1", features = ["full"] } - 02
Build a client and check the exit IP
The client is cheap to clone and shares its pool; build it once per proxy username.
src/main.rs18 linesuse std::time::Duration;use reqwest::{Client, Proxy};fn arc_client(username: &str) -> reqwest::Result<Client> { Client::builder() .proxy(Proxy::all("http://proxy.arcproxies.io:8000")?.basic_auth(username, "PASSWORD")) .connect_timeout(Duration::from_secs(10)) .timeout(Duration::from_secs(30)) .build()}#[tokio::main]async fn main() -> Result<(), Box<dyn std::error::Error>> { let client = arc_client("USERNAME_pool-arcfast_country-de")?; let body = client.get("https://api.ipify.org?format=json").send().await?.text().await?; println!("{body}"); Ok(())} - 03
One client per sticky session
A session handle in the username keeps the exit; the client keeps the connection. Mint a handle per task.
src/main.rs, continued17 linesuse std::time::{SystemTime, UNIX_EPOCH};fn new_handle() -> String { // any unique string works; use the rand crate in real code let n = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().subsec_nanos(); format!("{n:08x}")}async fn crawl_listing(start: &str) -> Result<(), reqwest::Error> { let user = format!("USERNAME_pool-arcfast_country-de_session-{}_sesstime-10", new_handle()); let session = arc_client(&user)?; for page in 1..=3 { let res = session.get(format!("{start}?page={page}")).send().await?; println!("{page} {}", res.status()); } Ok(())} - 04
Target a country or city
Swap the username per market. Cities come from the published list, lowercased without spaces; the examples use the residential pool.
inside an async fn10 lineslet markets = [ ("de-cologne", "USERNAME_pool-arcfast_country-de_city-cologne"), ("us-dallas", "USERNAME_pool-arcfast_country-us_city-dallas"), ("jp-tokyo", "USERNAME_pool-arcfast_country-jp_city-tokyo"),];for (market, user) in markets { let res = arc_client(user)?.get("https://example.com/price/123").send().await?; println!("{market} {}", res.status());} - 05
Detect 407, retry the rest
For an HTTPS target, a refused tunnel fails
send()before any response exists; walk the error's source chain to recognise it. For plain HTTP you get a response with status 407. Retry timeouts, 429 and 5xx with backoff on a new session.src/main.rs, continued31 linesuse std::error::Error as _;use reqwest::StatusCode;fn is_proxy_auth_error(err: &reqwest::Error) -> bool { let mut source = err.source(); while let Some(e) = source { if e.to_string().contains("proxy authorization required") { return true; } source = e.source(); } false}async fn fetch(url: &str) -> Result<String, Box<dyn std::error::Error>> { for attempt in 0..4u32 { let user = format!("USERNAME_pool-arcfast_country-de_session-{}_sesstime-5", new_handle()); match arc_client(&user)?.get(url).send().await { Ok(res) if res.status() == StatusCode::PROXY_AUTHENTICATION_REQUIRED => { return Err("proxy rejected the credentials".into()) } Ok(res) if res.status() != StatusCode::TOO_MANY_REQUESTS && !res.status().is_server_error() => { return Ok(res.text().await?) } Err(e) if is_proxy_auth_error(&e) => return Err("proxy rejected the credentials".into()), _ => {} // timeout, connection error, 429 or 5xx: retry } tokio::time::sleep(std::time::Duration::from_secs(1 << attempt)).await; } Err(format!("gave up on {url}").into())}
Proxy::all, Proxy::http and Proxy::https
| Constructor | Applies to | Use |
|---|---|---|
Proxy::all(url) | Every request | The normal choice for the gateway |
Proxy::https(url) | Only https:// targets | Mixed setups where plain HTTP goes direct |
Proxy::http(url) | Only http:// targets | Rarely useful for scraping |
Credentials can also sit in the proxy URL (http://user:pass@host:port); reqwest used them in our test. .basic_auth() avoids percent-encoding the password by hand.
Connection reuse decides when a rotating exit changes
A reqwest Client pools connections (idle ones close after 90 seconds by default), and an HTTPS connection through the gateway is one CONNECT tunnel with one exit. Two requests on one client shared a tunnel in our test. With a rotating username that means one exit per pooled connection; for a new exit per request, use a fresh session handle per request, and for a fixed exit, a sticky session.
With socks5h:// the gateway resolves the hostname, so DNS follows the exit country. Plain socks5:// resolves locally, which can pick a server near you instead.
The problem
Common pitfalls
No overall timeout
reqwest's timeout defaults to none. A stalled response through any proxy then waits indefinitely.
A Client per request
Each new client has an empty pool, so every request pays for a TCP connection, a tunnel and a TLS handshake. Build one per session and clone it.
Expecting HTTPS_PROXY to apply
Calling .proxy(...) disables the automatic system proxy. That is what you want; just do not rely on environment variables as well.
socks5 when you meant socks5h
socks5:// resolves DNS on your machine. For geo-targeted work use socks5h://.
Downloading what you do not need
Every byte through the gateway is billed per GB. reqwest only decompresses responses with its gzip, brotli or deflate features enabled; reducing proxy bandwidth lists more savings.
FAQ
Questions
Something not covered? Write to contact@arcproxies.io.
How do I use an authenticated proxy with reqwest?
Build the client with Client::builder().proxy(reqwest::Proxy::all("http://host:port")?.basic_auth("user", "pass")).build()?. reqwest sends the credentials as Proxy-Authorization, including on the CONNECT request for HTTPS sites.
Does reqwest support SOCKS5 proxies?
Yes, with the socks cargo feature. Use Proxy::all("socks5h://user:pass@host:port") so DNS is resolved at the proxy.
How do I use a different proxy per request in reqwest?
Proxies are set per Client, so keep one client per username (session or market) and choose the client per request. Clients are cheap to clone and share their pool.
How do I detect a 407 proxy error in reqwest?
For HTTPS targets send() returns an error with is_connect() true; its source chain contains "proxy authorization required". For plain-HTTP targets the response status is StatusCode::PROXY_AUTHENTICATION_REQUIRED.
Sources & review
Last reviewed by the ARC Proxies team. Prices are read from the live catalog at build time.
- [1]docs.rs: reqwest::Proxy (0.13)
- [2]docs.rs: reqwest::ClientBuilder (0.13)
- [3]docs.rs: reqwest::Error
- [4]docs.rs: reqwest feature flags
Next
What people build with it
Integrations
Other http clients
Get started
Get credentials and paste them in.
Sign up, buy a gigabyte, generate a username in the dashboard. Code WELCOME15 takes 15% off your first purchase; bandwidth never expires.