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.
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 , and the prior for any rare fingerprint is collapsing toward 1.
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
- If 403s happen before your request appears in upstream logs, suspect the TLS layer first — not your headers and not your proxies.
tcpdumpplus a JA3 hasher will tell you exactly which fingerprint you’re emitting. Don’t guess.- Impersonating a popular browser is not a forever fix — fingerprint lists drift. Pin a preset, version it in CI, and rotate every two months.