Rust · setup guide

Rust (reqwest) proxy setup

reqwest takes a proxy on the ClientBuilder: build a reqwest::Proxy with Proxy::all, Proxy::https or Proxy::http, add credentials with .basic_auth or in the URL, and pass it to .proxy(...). The client then pools connections, and a pooled tunnel keeps its exit address, so the pool settings decide whether you rotate.
You will need

Residentialresi.proxymonkey.io:8000

ISP or datacenterIP:PORT

CredentialsUSER:PASS

Copy your own from the dashboard, which also lists the host and port for every order. Where it differs from this page, the dashboard is right.

Install

Rust with Cargo. The examples use reqwest's async client on Tokio. Add --features socks to the first line only if you connect over SOCKS5.

terminal
cargo add reqwest
cargo add tokio --features full

Rotating residential

pool_max_idle_per_host(0) keeps no idle connections, so every request opens a new tunnel through the residential gateway and leaves from a new address. A new Client per request rotates too, but rebuilds its TLS setup each time; one client with no idle pool is cheaper.

src/main.rs
use std::time::Duration;

#[tokio::main]
async fn main() -> Result<(), reqwest::Error> {
    let client = reqwest::Client::builder()
        .proxy(reqwest::Proxy::all("http://USER:[email protected]:8000")?)
        .pool_max_idle_per_host(0)
        .timeout(Duration::from_secs(30))
        .build()?;

    for _ in 0..3 {
        let body = client.get("https://httpbin.org/ip").send().await?.text().await?;
        println!("{}", body.trim());
    }
    Ok(())
}

A static datacenter or ISP IP

On a static datacenter IP keep the pool, so requests reuse connections. .basic_auth takes the username and password as plain strings, so nothing needs escaping in a URL. For an ISP order, use the host and port your dashboard lists for it.

src/main.rs
use std::time::Duration;

#[tokio::main]
async fn main() -> Result<(), reqwest::Error> {
    let proxy = reqwest::Proxy::all("http://IP:PORT")?.basic_auth("USER", "PASS");
    let client = reqwest::Client::builder()
        .proxy(proxy)
        .connect_timeout(Duration::from_secs(10))
        .timeout(Duration::from_secs(30))
        .build()?;

    for url in ["https://httpbin.org/ip", "https://httpbin.org/headers"] {
        let resp = client.get(url).send().await?;
        let status = resp.status();
        let body = resp.text().await?;
        println!("{status} {}", body.len());
    }
    Ok(())
}

Keeping one identity

A Client keeps idle connections per host, and through the proxy each one is a tunnel with one exit address. Keep the default pool and requests to one site reuse those tunnels and their addresses until the connections idle out, which suits a short multi-step flow and surprises you when you expected rotation. reqwest keeps no cookies unless you enable its cookies feature and call .cookie_store(true). For an address that holds across new connections, use a static IP or a residential sticky session; the session setting for your account is in the dashboard.

Specific to Rust (reqwest)

Things worth knowing

SOCKS5 needs the socks feature

reqwest understands socks5:// and socks5h:// proxy URLs only with the socks feature on. socks5h sends the hostname to the proxy to resolve, so your own resolver never sees the lookup; socks5 resolves it locally. Use the SOCKS5 host and port your dashboard lists.

terminal + src/main.rs
cargo add reqwest --features socks

let proxy = reqwest::Proxy::all("socks5h://USER:PASS@IP:PORT")?;

Read the whole error chain

A failed request prints as error sending request for url (...), and the useful part, a 407 or a TLS failure, sits further down the chain of source() errors. Walk it, or print the error with {:?}.

report.rs
use std::error::Error;

fn report(err: &reqwest::Error) {
    let mut cause: Option<&dyn Error> = Some(err);
    while let Some(e) = cause {
        eprintln!("{e}");
        cause = e.source();
    }
}

Count what reqwest moved

We meter request bytes plus response bytes, headers included. reqwest asks for no compression unless you enable its gzip, brotli or zstd features, so by default the body you read is the size that arrived. The request count below misses the request line and the default headers the client adds as it sends, and TLS overhead makes ours slightly higher.

wire_bytes.rs
async fn wire_bytes(client: &reqwest::Client, url: &str) -> reqwest::Result<usize> {
    let head = |h: &reqwest::header::HeaderMap| {
        h.iter().map(|(k, v)| k.as_str().len() + v.len() + 4).sum::<usize>()
    };
    let req = client.get(url).build()?;
    let sent = head(req.headers());
    let resp = client.execute(req).await?;
    let received = head(resp.headers());
    Ok(sent + received + resp.bytes().await?.len())
}
When it breaks

Common errors and fixes

  • tunnel error: proxy authorization required at the end of the error chain

    Why
    The gateway answered the CONNECT for an HTTPS URL with a 407: the credentials were wrong, or a special character in a password written into the URL broke it.
    Fix
    Pass the credentials with .basic_auth("USER", "PASS") rather than in the URL, and copy them fresh from the dashboard.
  • operation timed out, with err.is_timeout() returning true

    Why
    The request ran past the .timeout you set. Without one, reqwest waits as long as the connection stays open, and a stalled residential exit can hold a request for minutes.
    Fix
    Set .connect_timeout and .timeout on the builder and retry on is_timeout(). With no idle pool on the rotating gateway, the retry leaves from a new address.
  • invalid peer certificate: UnknownIssuer, or certificate verify failed with native-tls

    Why
    The target's certificate, or a CA your system does not trust. reqwest runs TLS with the target through the tunnel; the proxy is not part of it.
    Fix
    Add the CA with .add_root_certificate on the builder. .danger_accept_invalid_certs(true) is for a one-off test only.
  • Your own IP comes back from https://httpbin.org/ip

    Why
    Proxy::http only covers http:// URLs, so HTTPS requests skip the proxy and go direct.
    Fix
    Use Proxy::all, or Proxy::https for HTTPS targets only.

A failed connection that moved no data is not billed. Retries you send are billed like any other request, and each shows as its own line in your usage log.

The community layer

Rust (reqwest) still misbehaving?

Paste the error and the few lines that set up the proxy into Discord, with the password taken out. Someone there has seen it before.

Join the Discord

4,200+monkeys in the Discord

  • Help from humans

    Post your error, get an answer. Usually in minutes, usually from someone who has hit the same wall.

  • A status bot that tells on us

    Pool health, incidents and maintenance posted automatically. Including the bad days.

  • Deals and free traffic

    Bonus GB drops, early access to new pools, and the occasional giveaway for a good bug report.

Join the Discord4,200+ monkeys, free to lurk