Privacy Tech

How Cloudflare Verification Actually Works (Playwright, 2026)

BC

BotCloud Team

May 4, 2026·10 min read

What "Bypass Cloudflare" Actually Means

There is no single Cloudflare check. When you navigate to a Cloudflare-protected site, your request flows through a stack of independent decisions. A clean fingerprint can pass three of them and fail the fourth. A clean IP can pass the static checks and fail the behavioral one. The umbrella term "Cloudflare bypass" hides this structure, which is why so many troubleshooting threads loop back to the same starting point.

This post breaks the stack into its five layers, walks through the error codes each layer emits, and explains what a real successful round-trip needs in practice.

The Five Layers

Cloudflare's edge runs these checks in roughly this order. Each layer can pass, fail outright, or escalate to the next.

Layer 1: Edge Filtering

IP reputation, ASN risk, country rules, and rate limits. This decision is made on the connection itself, before any TLS handshake or HTTP processing happens at the application layer.

  • Pass: connection accepted, hands off to TLS.
  • Fail outright: HTTP 1010 (no JA3/JA4 fingerprint match the customer allows), 1015 (rate-limited), 1020 (firewall rule denied), or a TCP reset.

Layer 2: TLS and HTTP/2 Fingerprint

The TLS ClientHello (cipher suite order, extensions, EC curves) produces a JA3 or JA4 hash. Many CF customers configure rules that allow only known-browser JA4s. HTTP/2 SETTINGS frames and pseudo-header order add another layer. Tools like curl-impersonate aim at this layer specifically.

  • Pass: handshake completes with a recognized fingerprint.
  • Fail: 1010 ("the owner of this website... has banned the autonomous system you are visiting from") is the typical error.

Layer 3: Bot Fight Mode (Static Score)

Cloudflare's enabled-by-default bot detection. It scores the connection based on User-Agent, common automation tells in HTTP headers, the presence of cf_clearance cookies from prior visits, and stored device-graph signals.

  • Pass: request reaches the origin or the optional Layer 4.
  • Fail: the request is dropped, served a CAPTCHA challenge, or rate-limited.

Layer 4: Managed Challenge (Interactive)

Either a Turnstile widget embedded in the page (signal-collecting widget plus invisible challenge) or a full-page interstitial titled "Performing security verification". The widget runs JavaScript checks that assemble the seven automation signals, plus canvas, audio, and font fingerprints.

  • Pass: a cf-turnstile-response token is issued and validated by the site's siteverify call.
  • Fail: the widget re-challenges, the page reloads, or the server-side siteverify returns success: false.

Layer 5: Application-Level Rate Limit

Even after a successful challenge, the site's own rate-limit configuration on the protected route can return 429 or queue the request. This is configured per-route by the site operator, not Cloudflare's default.

  • Pass: response from origin reaches the client.
  • Fail: 429 Too Many Requests, with an Cloudflare-emitted Retry-After header.

The Error Codes, Decoded

Cloudflare's error pages display a four-digit code and an unhelpful "ray ID". The codes that matter for automation:

CodeLayerWhat It MeansTypical Cause
1006 / 1007 / 10081IP banned at the firewallDatacenter IP, abuse-flagged residential IP, country block
10102Browser fingerprint failedTLS JA3/JA4 not on the allowed list, headless browser, missing extensions
10121Access deniedSite has an explicit block rule matching your request
10151Rate-limited at the edgeToo many requests from the IP
10201 / 3Access deniedFirewall rule (often based on the bot score)
5xx4 / 5Origin error or upstream timeoutOften surfaced when the challenge state machine breaks

The rule of thumb: 10xx codes are Cloudflare-side decisions and get fixed by changing the request fingerprint or IP. 5xx codes mean the request reached the origin, and the issue is with your script's interaction (form submission, missing token, etc.).

What an Actual Round-Trip Looks Like

To make the layers concrete, here are two timing logs from the same Playwright-driven session, captured against Cloudflare's public demo on 2captcha.com. Both runs used the same browser environment (a Chromium build with engine-level fingerprint patches, running on macOS, no proxy configured) and differed only in the demo's sitekey.

Run A: Test Sitekey (Forces Interactive Checkbox)

Sitekey 3x00000000000000000000FF, Cloudflare's documented test sitekey that forces a visible checkbox and issues a dummy token to any browser that clicks.

0 ms      page navigation starts
1,688 ms  DOM ready
3,681 ms  Cloudflare widget iframe loaded (Layer 4 entry)
4,808 ms  "Verify you are human" checkbox visible
6,299 ms  checkbox clicked
6,301 ms  cf-turnstile-response token populated (2 ms after click)
8,070 ms  page form submit clicked
8,453 ms  server returns "Captcha is passed successfully!"

End-to-end: 8.5 seconds. Click-to-token: 2 ms. Layer 4 was reached and resolved without escalation.

Run B: Real Sitekey (Production Customer)

Sitekey 0x4AAAAAAADnPIDROrmt1Wwj, the real Turnstile sitekey 2captcha uses on its Cloudflare Challenge demo page.

0 ms       page navigation starts
1,800 ms   DOM ready
3,452 ms   Cloudflare Challenge link clicked
3,853 ms   Cloudflare interstitial iframe loaded (Layer 4 entry)
7,432 ms   "Verify you are human" checkbox visible
7,454 ms   checkbox clicked
10,724 ms  Cloudflare iframe re-navigated (Layer 4 escalation)
27,457 ms  "Captcha is passed successfully!" never appears (timeout)

End-to-end: timeout. The widget rendered, the click registered, but Cloudflare did not issue a success token. Three seconds after the click, the iframe navigated to a fresh challenge URL — the textbook "infinite loop" symptom.

What These Two Runs Tell Us

The difference between the two is not the browser. The browser environment was identical. The difference is what Cloudflare validates after the click:

  • The test sitekey issues a dummy token to any browser. The 2 ms click-to-token confirms the integration plumbing works (iframe handshake, token DOM injection, form submission). It does not validate the browser environment against Cloudflare's risk model.
  • The real sitekey does validate. The widget rendered (so the static fingerprint score passed Layer 1-3), but the post-click decision factored in IP reputation, browsing history, and the absence of cf_clearance cookies, and decided to re-challenge.

This is what "verified across" means in practice: passing all five layers is not a property of the browser alone. The browser carries Layers 2 and 4 (TLS fingerprint and JavaScript fingerprint). The IP and session state carry Layers 1, 3, and 5. Skipping any one of these means the request reaches the next layer just to fail there.

What Stops the Loop

Working from the most common failure modes:

If You Are Hitting 1010

Your TLS fingerprint is the problem. Default Node.js or Python HTTP clients have JA3/JA4 hashes that are not on Cloudflare's allowed list. Browser-grade JA4 comes from running an actual browser, or from curl-impersonate configured to match a current Chrome. There is no fix at the page layer; if the TLS handshake fails, you never reach JavaScript.

If You Are Hitting 1015

You are being rate-limited, almost always at the IP layer. Reduce concurrency, slow the request rate, or switch to a different residential IP. If you have a pool, check that it is not all from the same /24 subnet.

If You Are Hitting 1020

A firewall rule denied your request. The rule could be IP-based, country-based, ASN-based, or based on the bot fight score. Check the response headers for cf-mitigated and the cf-cache-status. If the score is the issue, work the cluster of weak signals (Layer 4); if it is geo, switch the proxy region.

If the Widget Renders but Re-Challenges

You are passing Layers 1-3 but failing the post-click decision in Layer 4. The two main causes:

  1. IP reputation insufficient. The static fingerprint is fine but the IP is on a watchlist or has no cf_clearance history. Switch to a residential IP from a different pool.
  2. Behavioral or session signals. The click happened too fast, the mouse never moved before the click, or the session has zero history. Add a brief warm-up navigation pattern before the click.

If You Are Hitting 5xx After a Successful Challenge

The challenge passed but your subsequent request to the origin failed at the application layer. Inspect the request URL, headers, body, and the cookies that came back from the challenge response — usually a cf_clearance cookie that needs to be carried forward.

What an Engine-Level Browser Buys You

Layers 2 and 4 are the layers a browser controls. A Chromium build with engine-level fingerprint patches keeps these layers clean by construction:

  • Layer 2 (TLS / HTTP/2): the build's TLS stack matches a real Chrome. JA3 and JA4 hashes are real-Chrome hashes, not the variants that stealth-plugin'd Chromium produces.
  • Layer 4 (JavaScript fingerprint): navigator.webdriver is genuinely absent (not patched), CDP-injected globals do not exist on window, the canvas and audio fingerprints come from a per-context profile rather than runtime randomization. This is the difference between Run A and Run B not being explained by the browser side.

What the browser cannot do alone is pass Layers 1, 3, and 5 — those depend on IP reputation, prior cookie state, and the application-layer rate limit configured per route. The full "verified across" outcome is the product of an engine-level browser plus a clean residential proxy plus a non-empty session warm-up. None of the three substitute for the others.

Diagnostic Workflow

When you run into a Cloudflare wall, work through this in order:

  1. Get the error code. If there is no error page, look at the response status, the cf-mitigated header, and the cf-ray for tracing.
  2. Identify which layer rejected you. 10xx codes locate it for you. Re-challenges in the iframe locate it at Layer 4. 5xx with cf-cache-status: DYNAMIC locates it past Cloudflare into your own integration.
  3. Test the layer in isolation. For Layer 2, run curl --tlsv1.2 and check JA3 against tools.peetch.com. For Layer 4, log the page-level probe (navigator.webdriver, Object.keys(window) filtered by automation patterns) and the request headers.
  4. Change one variable at a time. Different proxy. Different browser engine. Add a warm-up. Do not change all three at once; you will not learn which fixed it.
  5. Verify with a known-good reference. Run the same script against bot.sannysoft.com or creepjs first to verify the browser side; then run it against the real target. If the reference passes and the target fails, the difference is the IP or the route.

Quick FAQ

Q: Is bypassing Cloudflare legal?

The legal question depends on the target site's terms of service, the jurisdiction, and the purpose. Privacy research, accessibility testing, and authorized security testing are generally fine. Scraping copyrighted content, evading paywalls, or violating explicit ToS clauses creates legal exposure regardless of the technical method.

Q: What is the success rate?

There is no fixed number. The factors that move it: engine-level vs JS-patched browser (the largest single factor), residential vs datacenter IP, session warm-up presence, rate of requests, and the specific Cloudflare configuration on the target route. A typical clean stack on a low-stakes target site is in the 95-99% range. A high-stakes target with an aggressive bot fight configuration drops well below that, and no single tool gets it to 100%.

Q: How does Cloudflare detect bots?

Through the five layers above, weighted by a proprietary risk model. The cluster of weak signals matters more than any single signal. Patching navigator.webdriver removes one; the model is still summing fifteen others.

Q: Cloudflare 1015 vs 1020 — what is the difference?

1015 is rate-limit. 1020 is firewall rule. 1015 lifts on its own after the cooldown; 1020 is a binary decision that requires a different IP or fingerprint.

Q: Will solving services help?

For Layer 4 specifically, yes — they produce a token by completing the challenge in their own browser pool. For Layers 1, 2, 3, and 5, no — those are evaluated on your request, not the solver's. A token bought from a solver service will fail siteverify if your request reaches the server with a flagged IP.

Bottom Line

Cloudflare verification is not one challenge. It is five layers, each with its own pass criteria and its own error codes when it fails. A browser engine controls Layers 2 and 4; the network and session state control Layers 1, 3, and 5. The "verified across" outcome is the product of all three handled correctly, and the failure mode you see depends on which layer rejected you.

If you are choosing a browser engine for your stack, see BotCloud's cloud browser for an engine-level Chromium build with a per-context fingerprint API.

#cloudflare#playwright#automation#challenge#turnstile

Share this post