Irmu
Engineering16 minIrmu Engineering

Cloudflare Bypass: What Actually Works in 2026

A technical look at Turnstile, TLS fingerprinting and why most open-source bypasses stopped working.

There is no bypass, there is a score

The framing of "bypassing Cloudflare" implies a door with a lock. It is closer to a credit check. Every request accumulates a bot score from dozens of independent signals, and the site operator chooses thresholds: below one number you get the page, above it a managed challenge, above another a hard block. Nothing you do flips a boolean — you move a number.

That reframing matters because it explains why fixes are cumulative and why they decay. Adding a realistic user agent moves the number a little. Adding a matching TLS fingerprint moves it more. Getting the IP reputation right often moves it most. And a single glaring inconsistency — a Chrome 140 user agent on a TLS handshake that only OpenSSL produces — can outweigh everything you got right.

The signals that actually carry weight

Roughly in order of how much they move the score for a typical scraper, based on what fails first when you strip them away:

  • IP reputation and ASN. A datacenter ASN with abuse history starts you in a hole no browser realism can dig you out of.
  • TLS fingerprint (JA3/JA4). Cipher suite order, extensions and supported groups identify your HTTP client regardless of headers.
  • HTTP/2 fingerprint. SETTINGS frame values, window size, header pseudo-order and priority frames differ per client and are rarely spoofed.
  • Header order and casing. Browsers emit a stable, ordered set; most HTTP libraries emit an alphabetized or insertion-ordered one.
  • Browser environment entropy. Canvas, WebGL vendor strings, audio context, screen metrics, font list, `navigator.webdriver`.
  • Behavioural timing. Instant clicks, zero mouse movement, perfectly regular request intervals.

Why your requests library gives you away instantly

You can set every header a browser sends and still be identified before a single byte of HTTP is exchanged, because the TLS ClientHello already fingerprinted you. This is the part people find genuinely surprising, so it's worth seeing.

The two requests below send identical headers. One is fingerprinted as Python, the other as Chrome. Nothing in the HTTP layer distinguishes them.

fingerprint_demo.py
import requests, curl_cffi.requests as cffi

HEADERS = {
    "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) "
                  "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0 Safari/537.36",
    "accept-language": "en-US,en;q=0.9",
}

# JA3 says: Python/OpenSSL. Score penalty applied before HTTP is parsed.
a = requests.get("https://tls.peet.ws/api/all", headers=HEADERS).json()

# JA3 says: Chrome 140. Same headers, different handshake.
b = cffi.get("https://tls.peet.ws/api/all", headers=HEADERS,
             impersonate="chrome131").json()

print(a["tls"]["ja3_hash"])   # e.g. 3fed133de60c35724739b913924b6c24
print(b["tls"]["ja3_hash"])   # e.g. cd08e31494f9531f560d64c695473da9

If you take one thing from this article: check your JA3 before you debug your headers. Half of "Cloudflare blocks me" tickets are solved at the TLS layer.

The four things you'll actually run into

It helps to name what you're looking at, because the right response differs completely.

  • JS challenge (the interstitial): a page that computes a proof and reloads. Needs a real JS engine; a plain HTTP client will loop forever.
  • Turnstile: an embedded widget. Sometimes non-interactive, sometimes a checkbox. Sensitive to environment entropy and IP reputation, not to how fast you click.
  • Rate limiting (429 or 1015): you were identified as fine, just too eager. Fix your concurrency, not your fingerprint.
  • Hard block (1020 / access denied): a WAF rule matched — often your ASN, country or a path pattern. More requests will not help; something structural has to change.
python
def classify(status: int, html: str) -> str:
    if status in (429,) or "error code: 1015" in html:
        return "rate_limited"        # back off, lower concurrency
    if "error code: 1020" in html or "Access denied" in html:
        return "hard_block"          # change ASN/geo, or stop
    if "cf-turnstile" in html or "challenge-platform" in html:
        return "challenge"           # needs a real browser environment
    if status == 403 and "Just a moment" in html:
        return "js_challenge"
    return "ok"

Why open-source bypasses decay

Patched browser builds and stealth plugins work — right up until the detection vendor adds a check for the patch itself. The plugin that hides `navigator.webdriver` leaves a detectable seam in the property descriptor. The patched Chromium has a build fingerprint. The public solver's request pattern is, itself, a signal shared by everyone using it.

The lifecycle of a public bypass is typically weeks to a few months, and the failure mode is the cruel one: it fails silently. You keep getting 200s. They contain challenge HTML. Your parser finds no products, writes nulls, and your dashboard shows a gentle decline that nobody investigates for a fortnight.

This is not an argument against those tools — they're good work, and for a hobby project they're fine. It's an argument that the maintenance is the product, and that you should decide deliberately whether you want to own it.

validate.js
const { html, status } = await irmu.scrape({ url, render: true });

// Validate content, not just status. This is the check that saves datasets.
const isChallenge =
  html.includes("cf-turnstile") ||
  html.includes("challenge-platform") ||
  html.includes("Just a moment");

if (status !== 200 || isChallenge || !html.includes("data-product-id")) {
  metrics.increment("scrape.soft_block", { host: new URL(url).host });
  throw new Error("Challenge page or layout change detected");
}

What to do at each scale

Being blunt about where the line sits, because the honest answer isn't always "buy the API".

  • A few hundred requests a day, one site: `curl_cffi` with impersonation plus a decent residential IP will very likely be enough. Don't overbuild.
  • Tens of thousands a day, a handful of sites: you'll need real browsers, session management and per-domain backoff. Doable in-house; expect one engineer's ongoing attention.
  • Millions a month, or many different sites: the fingerprint, IP-reputation and challenge layers change under you continuously. This is where owning it stops being a project and becomes a team.

The infrastructure answer

Sustainable access comes from coherence across the whole profile: TLS and HTTP/2 fingerprints that match the claimed browser, residential IPs with clean reputation in the right geography, plausible interaction timing, per-domain concurrency limits, and challenge handling when a challenge does appear.

That is maintenance work with no end date, which is precisely the argument for buying it rather than building it. Irmu runs the fingerprint and IP-reputation layer as a service and retries internally when a response comes back as a challenge — but be clear on the billing: if we deliver you a response, including an anti-bot page or a 404, that is a successful, billed request. Only failures on our side and uncovered domains are free. So keep the content assertion in your code regardless of who is fetching the page.

bash
curl -G https://app.irmu.com/api/crawl \
  -H "Authorization: Bearer $IRMU_API_KEY" \
  --data-urlencode "url=https://protected.example.com/catalog" \
  --data-urlencode "js=true" \
  --data-urlencode "premium=true" \
  --data-urlencode "country=de"

One last thing about ethics and rate

Anti-bot systems exist partly because scrapers behaved badly. The single most effective thing you can do for your own long-term access is to be a cheap guest: cap concurrency per domain, honour 429s with real backoff, cache aggressively, crawl during off-peak hours, and never touch anything behind a login you weren't given. Sites tighten their thresholds in response to load. Being unremarkable is a strategy.

Start building with Irmu today

200 free credits every month, no card required. Every API, every integration, one key.