Most advice about scraping Cloudflare-fronted sites is asserted, not measured. "Use residential IPs." "Match Chrome's JA3." People repeat these like folklore. We run a proxy network, so we had the tooling to actually test them — and the results contradicted the two most common pieces of advice.
Here's what we ran, and what failed.
The test
Two variables, four combinations:
- Exit IP: a Psychz datacenter address vs a Frontier Communications residential line (both US).
-
TLS stack: plain Python
requestsvscurl_cffiimpersonating Chrome.
Seven Cloudflare-fronted sites, two passes each — 28 requests total, US exits, 27 July 2026. A "pass" required HTTP 200 and a body without challenge markers. A 200 that returns "Just a moment…" is a block wearing a success code, and it does not count.
The numbers
| Combination | Passed |
|---|---|
| Datacenter IP + default Python TLS | 0 / 7 |
| Datacenter IP + Chrome-impersonated TLS | 0 / 7 |
| Residential IP + default Python TLS | 0 / 7 |
| Residential IP + Chrome-impersonated TLS | 0 / 7 |
Every combination failed identically. Two things in that table contradict advice you'll read everywhere.
The residential IP bought us nothing
Not a single site treated the Frontier residential exit differently from the Psychz datacenter one. That does not mean IP reputation is a myth — it means these sites never got as far as judging our IP. A request that fails the bot-management check is refused on other grounds first.
IP quality decides outcomes in the middle of the difficulty curve. At the hard end it isn't the binding constraint, and buying better IPs won't move it. If you're stuck at 403 on a genuinely aggressive Cloudflare config, a residential proxy is not the missing piece.
Chrome TLS impersonation didn't flip a single site either
curl_cffi with impersonate="chrome" is great tooling, but on these targets it changed nothing. The sites require executing the JavaScript challenge, which no HTTP client does, no matter how convincing its TLS handshake. If you're stuck, adding a TLS library isn't the fix — you need something that runs JS.
For calibration: the same script sailed through Cloudflare-fronted sites that aren't running aggressive bot mode, on every combination including plain Python from a bare datacenter host. "Protected by Cloudflare" spans an enormous range of strictness. Establish which end your target sits at before spending money on tooling.
Bonus: stop chasing a specific JA3 hash
While measuring, we checked what fingerprint our own requests presented. Three consecutive curl_cffi requests with the same impersonate="chrome" setting, from the same host, produced three different JA3 hashes (a0052cf3…, 0899dce7…, d2de58db…).
That's not a bug. Real Chrome shuffles TLS extensions and injects GREASE values, so its JA3 changes per connection — and a good impersonation library reproduces that. Any guide telling you to match one specific JA3 string is describing a browser that stopped existing years ago.
What did stay identical across all our requests — direct, via residential proxy, and via datacenter proxy — was the HTTP/2 fingerprint (52d84b11…), derived from the SETTINGS frame, header table size, window size and pseudo-header order. It's far more stable than JA3, which is exactly why it's worth more to the people fingerprinting you. Check both when you debug a silent block; we used tls.browserleaks.com/json.
So what actually works?
Match the tool to where your target sits on the curve:
- Soft Cloudflare (rate limiting, light bot mode): an HTTP client with a real browser TLS impersonation and a decent IP is plenty. This is most of the web.
- Hard Cloudflare (interactive challenge, Turnstile): you need a real browser that executes the JS — Playwright or Puppeteer. The proxy's job there is IP diversity, not passing the check.
The mistake I see most often is spending money on better proxies to solve a problem that lives in the JavaScript layer. Test the cheap hypothesis first: does plain Python already work? Does a headless browser with no proxy work? Only then start paying for exit quality.
Reproduce it
The method is boring on purpose: 28 requests, 7 sites, 4 tool combinations, pass = 200 with no challenge markers in the body. If you want to run the same matrix against your own targets, the request-side code (requests, httpx, curl_cffi, Playwright) is in a public examples repo: github.com/roamproxy/proxy-examples.
If you want a residential pool to test the "does a better IP help my target" hypothesis without a subscription, the network I work on (Roam) gives 300MB free on signup — enough to run the matrix above against your actual target before spending anything.
Got a Cloudflare block that behaves differently from this? I'd genuinely like to hear it — drop the details in the comments.
0 Comments
Log in to join the conversation.No comments yet. Be the first to share your thoughts.