Back to all posts
Configuring Proxies in Rust for High-Throughput Web Scraping

Configuring Proxies in Rust for High-Throughput Web Scraping

August 20, 2026

Why Rust for High-Throughput Scraping

Rust's zero-cost abstractions, fearless concurrency, and fine‑grained control over memory make it a natural fit for scrapers that must issue thousands of requests per second. When you combine Rust's async runtime (tokio or async-std) with a well‑tuned proxy layer you get predictable latency, low CPU overhead, and the ability to saturate bandwidth without the garbage‑collection pauses that plague managed languages.

Choosing the Right Proxy Protocol

HTTP/HTTPS

Most public APIs and websites speak HTTP/1.1 or HTTP/2 over TLS. An HTTP proxy forwards the request unchanged, adds the Proxy-Authorization header when needed, and can cache responses. It works seamlessly with Rust's reqwest crate.

SOCKS5

SOCKS5 operates at the TCP layer, so it can tunnel any protocol (including WebSocket, FTP, or custom TCP services). It also supports UDP relay and authentication. Use SOCKS5 when you need to scrape non‑HTTP endpoints or when the target blocks HTTP‑proxy headers.

Setting Up a Proxy Client in Rust

Adding Dependencies

Add the following to Cargo.toml. The socks feature enables SOCKS5 support in reqwest.

[dependencies]
reqwest = { version = "0.12", features = ["rustls-tls", "socks", "json", "gzip"] }
tokio = { version = "1", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }
anyhow = "1.0"

Building a Reqwest Client with Proxy

use reqwest::Client;
use std::time::Duration;

fn build_client(proxy_url: &str) -> anyhow::Result<Client> {
    let proxy = reqwest::Proxy::all(proxy_url)?;
    let client = Client::builder()
        .proxy(proxy)
        .timeout(Duration::from_secs(10))
        .pool_idle_timeout(Duration::from_secs(30))
        .pool_max_idle_per_host(100)
        .build()?;
    Ok(client)
}

The builder sets a global request timeout, an idle‑connection timeout, and a generous per‑host connection pool — essential for high‑throughput workloads.

Implementing Proxy Rotation

Simple Round‑Robin Rotator

use std::sync::Arc;
use tokio::sync::Mutex;

pub struct Rotator {
    proxies: Vec<String>,
    idx: Mutex<usize>,
}

impl Rotator {
    pub fn new(proxies: Vec<String>) -> Self {
        Self { proxies, idx: Mutex::new(0) }
    }

    pub async fn next(&self) -> String {
        let mut idx = self.idx.lock().await;
        let proxy = self.proxies[*idx].clone();
        *idx = (*idx + 1) % self.proxies.len();
        proxy
    }
}

Wrap the rotator in an Arc and share it across worker tasks. Each task calls rotator.next().await before building a client.

Handling Failures and Retries

Transient proxy errors (5xx, connection reset, timeout) should trigger a retry with a fresh IP. A lightweight policy:

async fn fetch_with_retry(
    rotator: &Arc<Rotator>,
    url: &str,
    max_attempts: usize,
) -> anyhow::Result<String> {
    let mut attempt = 0;
    loop {
        let proxy = rotator.next().await;
        let client = build_client(&proxy)?;
        match client.get(url).send().await {
            Ok(resp) if resp.status().is_success() => return Ok(resp.text().await?),
            Ok(resp) if resp.status().is_server_error() => {}
            Err(_) => {}
        }
        attempt += 1;
        if attempt >= max_attempts {
            anyhow::bail!("exhausted proxy pool after {} attempts", max_attempts);
        }
        tokio::time::sleep(Duration::from_millis(200 * attempt as u64)).await;
    }
}

Exponential back‑off prevents hammering a single failing proxy.

Tuning Timeouts and Connection Pooling

  • Request timeout – 8‑12 seconds is a good starting point for most sites.
  • Idle timeout – Keep connections alive for 30‑60 seconds to reuse TLS sessions.
  • Pool sizepool_max_idle_per_host of 100‑200 lets you sustain thousands of concurrent requests without opening new sockets for each.
  • TCP keepalive – Enable at the OS level (net.ipv4.tcp_keepalive_time) to detect dead proxies early.

Monitoring Proxy Health

Instrument each request with latency, status code, and proxy identifier. Export metrics to Prometheus via the metrics crate and visualise in Grafana. Alert when:

  • Error rate > 5 % for a given proxy over 1 minute.
  • Median latency > 2 seconds.
  • Connection‑pool exhaustion events. Automated health checks let you evict bad IPs before they poison the rotator.

Putting It All Together: A Mini Scraper

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let proxy_list = vec![
        "http://user:pass@proxy1.roproxy.com:8000",
        "socks5://user:pass@proxy2.roproxy.com:1080",
        // … more entries
    ];
    let rotator = Arc::new(Rotator::new(proxy_list));
    let urls = vec!["https://example.com/page1", "https://example.com/page2"];

    let tasks: Vec<_> = urls
        .into_iter()
        .map(|url| {
            let rot = rotator.clone();
            tokio::spawn(async move {
                match fetch_with_retry(&rot, &url, 5).await {
                    Ok(body) => println!("OK {} bytes from {}", body.len(), url),
                    Err(e) => eprintln!("FAIL {}: {}", url, e),
                }
            })
        })
        .collect();

    for t in tasks { t.await?; }
    Ok(())
}

Run with cargo run --release. The program spawns one task per URL, each pulling a fresh proxy from the rotator, retrying on failure, and printing results.

Tips for Production Deployments

  • Store proxy credentials in a secret manager (HashiCorp Vault, AWS Secrets Manager) and inject them at container start.
  • Run the rotator as a separate service with a gRPC/HTTP API so multiple scraper instances share a single source of truth.
  • Use reqwest::Client as a long‑lived singleton per worker to benefit from connection pooling.
  • Enable rustls TLS backend for lower memory footprint compared to OpenSSL.
  • Periodically refresh the proxy list from your provider (e.g., RoProxy's API) to keep the pool fresh.

How RoProxy Fits In

RoProxy offers both residential and ISP/static endpoints with automatic IP rotation, geo‑targeting, and SOCKS5 support. By pulling the endpoint list from RoProxy's dashboard API you can keep the proxy_list in the example up‑to‑date without manual changes. The service also exposes per‑IP health metrics that map directly to the Prometheus alerts described above, turning proxy management into an observable, self‑healing component of your scraping pipeline.