1 MIN READ · 14 MAR 2026 · scraping

Overcoming Cloudflare's TLS fingerprinting in Rust

Why reqwest's default rustls handshake gets blocked at the gateway, and the JA3-spoof handshake that gets the scraper through.

SB
Steven Battilana Quant · Zürich · ex-ETH

The Hook

While scraping high-velocity event-resolution data from Polymarket, our otherwise-clean Rust scraper started taking 403 Forbidden at the gateway. Not on the application — at the TLS layer. Cloudflare was rejecting our client hello before any HTTP byte left the wire.

The fix was small, but the diagnosis was not: rotating IPs didn’t move the needle, rotating User-Agents didn’t either. The block was on our JA3 fingerprint — a hash of TLS ClientHello extensions, cipher suites and EC curves that’s leaking the moment your socket opens.

The Context

Out-of-the-box, reqwest with rustls emits a ClientHello that hashes to a JA3 used by almost no real browser on Earth. Cloudflare keeps a blocklist of these “uncommon” fingerprints, and once a fingerprint shows up too often on a single ASN, it goes on the wall.

The mathematical view: Cloudflare is approximating a posterior P(θx)P(xθ)P(θ)P(\theta|x) \propto P(x|\theta) P(\theta), and the prior P(bot)P(\text{bot}) for any rare fingerprint is collapsing toward 1.

01x2dx=13\int_0^1 x^2 \, dx = \frac{1}{3}

The cheapest move on our side is to push our fingerprint into the head of the distribution of real browsers — so the posterior collapses the other way.

The Code

Swap reqwest’s default rustls backend for one whose ClientHello matches Chrome’s:

use rustls_impersonate::ChromePreset;
use reqwest::ClientBuilder;

pub fn build_client() -> reqwest::Result<reqwest::Client> {
    let tls = ChromePreset::v122()
        .with_grease(true)
        .with_alpn(&["h2", "http/1.1"])
        .build();

    ClientBuilder::new()
        .use_preconfigured_tls(tls)
        .http2_prior_knowledge()
        .build()
}

The handshake takes the same RTT and the API surface is unchanged.

Diagnose at the layer the block lives in. The cheapest TLS change beat a week of proxy-rotation work.

The Takeaways

  1. If 403s happen before your request appears in upstream logs, suspect the TLS layer first — not your headers and not your proxies.
  2. tcpdump plus a JA3 hasher will tell you exactly which fingerprint you’re emitting. Don’t guess.
  3. Impersonating a popular browser is not a forever fix — fingerprint lists drift. Pin a preset, version it in CI, and rotate every two months.
Posted 14 MAR 2026 · filed under Scraping