# How to Use a Proxy in Rust: A reqwest Client Guide

If you're building an HTTP client in Rust and need to route requests through a proxy, reqwest handles it natively. You don't need a separate crate.

Build a `Proxy` with `reqwest::Proxy::http()`, pass it to `ClientBuilder::proxy()`, and every request made with that client goes through it. That's the whole setup.

Here's the minimal version:

```rust
use reqwest::{Client, Proxy};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::builder()
        .proxy(Proxy::http("http://proxy-host:proxy-port")?)
        .build()?;

let body = client
    .get("https://example.com")
    .send()
    .await?
    .text()
    .await?;

println!("{body}");
Ok(())

}
```

That's it. Every request made with `client` now goes through the proxy.

## HTTP-only, HTTPS-only, or both

reqwest gives you three constructors depending on what you want routed:

*   `Proxy::http(url)` — proxies HTTP requests only
    
*   `Proxy::https(url)` — proxies HTTPS requests only
    
*   `Proxy::all(url)` — proxies everything, regardless of scheme
    

Most setups just want everything routed the same way, so `Proxy::all()` is usually the one you reach for:

```rust
let client = Client::builder()
    .proxy(Proxy::all("http://proxy-host:proxy-port")?)
    .build()?;
```

If you need different proxies for different destinations; say, one proxy for a specific domain and a fallback for everything else, chain multiple `.proxy()` calls on the builder, or use `Proxy::custom()` with a closure that returns the right proxy URL per request.

## Adding authentication

Most paid proxies need a username and password. reqwest has a method for exactly this, you don't build the header yourself:

```rust
let proxy = Proxy::all("http://proxy-host:proxy-port")?
    .basic_auth("username", "password");

let client = Client::builder().proxy(proxy).build()?;
```

`basic_auth()` sets the `Proxy-Authorization` header on every request that goes through this proxy. If your provider expects a non-Basic auth scheme, `custom_http_auth()` takes a raw header value instead, so you're not locked into Basic if your provider doesn't use it.

## Using environment variables instead of hardcoding

If you don't call `.proxy()` at all, reqwest still checks `HTTP_PROXY`, `HTTPS_PROXY`, and `NO_PROXY` in your environment and uses them automatically:

```rust
let client = Client::builder().build()?;
// picks up HTTP_PROXY / HTTPS_PROXY / NO_PROXY if they're set
```

This is on by default, so it's worth knowing how to turn off if you want a client that never proxies, regardless of what's in the environment:

```rust
let client = Client::builder()
    .no_proxy()
    .build()?;
```

`no_proxy()` here disables all proxying on the client, including the automatic environment-variable lookup. That's different from `Proxy::no_proxy()`, which sets a bypass list on one specific proxy, don't confuse the two.

## Turning on SOCKS5 support

reqwest's SOCKS5 support sits behind a Cargo feature, so if your proxy provider gives you a `socks5://` address, add the feature first:

```[dependencies]
[dependencies]
reqwest = { version = "0.12", features = ["socks"] }
```

Then point `Proxy::all()` at the SOCKS5 URL the same way you would an HTTP one:

```rust
let client = Client::builder()
    .proxy(Proxy::all("socks5://proxy-host:proxy-port")?)
    .build()?;
```

Skip this step and you'll get a compile error the moment you try to build a `socks5://` proxy URL, not a runtime failure, so it's an easy one to catch early.

## Confirming the proxy is actually being used

A `200 OK` response isn't proof the proxy is in the request path. If your proxy config is silently ignored, you'll still reach the target site directly and never notice.

Hit an IP-echo endpoint with and without the proxy, and compare:

```rust
let body = client
    .get("https://api.ipify.org")
    .send()
    .await?
    .text()
    .await?;
```

If the returned IP matches your proxy's IP, not your machine's, the proxy is doing its job. If it matches your own connection, check that the `Client` you're using is the one you built with `.proxy()`, it's easy to accidentally send a request through a plain `Client::new()` left over from earlier in the code.

## Handling the errors you'll actually hit

Three errors show up most often once you're running this against a real proxy:

`error sending request... error trying to connect: tcp connect error` The proxy host or port is wrong, or the proxy is down. Check the address before debugging anything else in your code.

`407 Proxy Authentication Required` Your credentials are missing or wrong. If a password contains special characters, make sure it's URL-encoded before it goes into the proxy URL string, or pass it through `basic_auth()` instead of embedding it in the URL.

**Requests that hang and eventually time out** Set an explicit timeout so a dead proxy fails fast instead of hanging your program:

```rust
let client = Client::builder()
    .proxy(Proxy::all("http://proxy-host:proxy-port")?)
    .timeout(std::time::Duration::from_secs(10))
    .build()?;
```

Without this, a client using a bad proxy can hang far longer than you'd expect, reqwest doesn't apply a request timeout by default.

## Putting it together

For most Rust projects, this is enough:

```rust
use reqwest::{Client, Proxy};
use std::time::Duration;

fn build_proxy_client(
    proxy_url: &str,
    username: &str,
    password: &str,
) -> Result<Client, reqwest::Error> {
    let proxy = Proxy::all(proxy_url)?.basic_auth(username, password);

    Client::builder()
        .proxy(proxy)
        .timeout(Duration::from_secs(10))
        .build()
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = build_proxy_client("http://proxy-host:proxy-port", "username", "password")?;

    let resp = client.get("https://example.com").send().await?;
    println!("status: {}", resp.status());
    Ok(())
}
```

One `Client`, built once, reused across every request that needs to go through the proxy. No dependencies beyond reqwest itself, plus the `socks` feature if you're on SOCKS5.

If you're testing this against a real proxy pool rather than a single static proxy, keep in mind that providers billing [per successfully authenticated IP](https://www.proxiesthatwork.com/) expect exactly this kind of username/password setup, so the code above should work against most of them without changes beyond the host, port, and credentials.
