Skip to content

Cloudflare's cf_clearance cookie: issuance, scope, and lifetime

· 18 min read
Copyright: MIT
cf_clearance wordmark with the cookie's SameSite=None; Secure; Partitioned attribute string

You solve a Cloudflare challenge once, and for the next half hour the site stops asking. No more spinning “Checking your browser.” No more interstitial. The thing that bought you that quiet is a single cookie named cf_clearance, set on your browser the moment the challenge verified. Copy that cookie into a fresh HTTP client, replay it from your server, and it dies on the first request. Same cookie, same value, same site, and yet the second client gets challenged anyway. Why?

That question is the whole post. cf_clearance is a proof-of-passage token, not a session cookie in the ordinary sense, and Cloudflare ties it to enough of the original request that lifting the string alone gets you nothing. We will trace where the cookie comes from, what it carries, what it is bound to, how far its scope reaches across a zone and across sites, how long it lives and who decides that, and the specific reasons a valid value stops being valid the instant the context around it changes.

The route runs roughly like this. First the issuance path: which challenge types mint the cookie and at what point in the flow. Then the cookie’s attributes as they appear on the wire, including the SameSite and Partitioned flags that govern cross-site behaviour. Then binding and scope, the part that matters most for anyone wondering why a copied cookie fails. Then lifetime, the Challenge Passage setting and its quirks. Then the Turnstile pre-clearance path, which issues the same cookie by a different door. And a closing look at where the cookie sits relative to __cf_bm and the bot score, because the two are constantly confused.

cf_clearance is set when a visitor passes a Cloudflare challenge. That is the one-line version, and Cloudflare’s own documentation states it plainly: when a visitor successfully solves a challenge, Cloudflare sets a cf_clearance cookie in the browser, and on subsequent requests Cloudflare evaluates that cookie before deciding whether to challenge again. If the cookie is present and valid, the request passes without an interstitial.

The “challenge” that triggers issuance covers several mechanisms. A managed challenge that resolves to a non-interactive JavaScript test, a JS challenge, an interactive challenge that asks for a click, or a Turnstile widget operating in pre-clearance mode all end the same way: a Set-Cookie: cf_clearance=... on the response that follows a verified solution. The cookie is the receipt. (For the differences between those challenge flavours, the managed vs JS vs interactive breakdown covers the decision logic; the cf-chl orchestration post covers the platform that runs them.)

The timing is the part worth being precise about, because it shapes everything downstream. The cookie is not set when the challenge page loads. It is set after the browser submits a solution and the Cloudflare edge verifies that solution. The challenge page itself fetches its payload from a path under /cdn-cgi/challenge-platform/, runs the client-side work, and POSTs the result back. Only a verified POST produces the clearance. Defensive analyses of the flow describe this same shape: a GET to the challenge-platform endpoint to pull the challenge, client-side execution, then a POST carrying the solution, with cf_clearance arriving on the response to that POST. The exact body of that POST is not publicly documented, and the field layout has changed across challenge-platform versions, so anyone quoting a specific schema for it is quoting a snapshot, not a stable contract.

Browser Cloudflare edge GET / (no clearance cookie) 403 challenge page + cf_chl_* cookies GET /cdn-cgi/challenge-platform/... POST solution (client work) Set-Cookie: cf_clearance=... GET / (cookie present) → 200 *The cookie lands on the response to a verified solution POST, not when the challenge page first appears. Everything before the orange arrows is setup.*

During the challenge itself, the platform also sets short-lived internal cookies. Cloudflare’s cookie reference lists cf_chl_rc_i, cf_chl_rc_ni, and cf_chl_rc_m as Challenge Platform cookies used to identify production issues with the challenge flow. Those are not the clearance. They are scaffolding for the challenge page; cf_clearance is the durable artifact that survives the challenge and gets presented on later requests.

Strip away the value and the cf_clearance cookie has a fixed and documented attribute set. Cloudflare states that it issues the cookie with SameSite=None; Secure; Partitioned. Each of those three flags is load-bearing.

Secure means the cookie only travels over HTTPS. Cloudflare’s documentation notes that a site not served over HTTPS can hit problems with the clearance cookie for exactly this reason, and that without HTTPS the cookie effectively falls back to SameSite=Lax, which then nullifies the Partitioned attribute because partitioning requires SameSite=None; Secure to take effect. So on a plain HTTP origin the cross-site machinery quietly stops working.

SameSite=None is the flag that lets the cookie ride along on cross-site requests at all. Cloudflare’s stated reason is that visitor requests coming from different hostnames should not be met with later challenges or errors. If the cookie were SameSite=Lax or Strict, a cross-site subrequest, an embedded resource, a fetch from another origin, would arrive without the clearance and get challenged. None keeps the proof attached in third-party contexts.

Partitioned is the newest of the three and the one that changes the cookie’s reach. It implements CHIPS, Cookies Having Independent Partitioned State, the Chrome mechanism that survives the deprecation of unpartitioned third-party cookies. With Partitioned set, a cf_clearance cookie issued inside a third-party context, an iframe or a cross-site subresource, is stored in a jar keyed to the top-level site rather than in one global jar shared across every site that embeds the same Cloudflare-protected resource. Cloudflare applies the same Partitioned treatment to the cf_chl_* challenge-platform cookies. This is a real behaviour change with a real consequence for scope, which we will get to.

cf_clearance= opaque, server-validated token attributes (documented): Secure SameSite=None Partitioned HTTPS only · cross-site allowed · jar keyed to top-level site (CHIPS) max cookie size 4096 bytes *The value is opaque to the client and validated at the edge. The three flags are the documented contract; the token's internal layout is not.*

Two more facts about the wire form. The cookie cannot exceed 4096 bytes, a hard ceiling Cloudflare names in the clearance documentation. And the value itself is opaque: a client cannot read meaning out of it, cannot edit it, and cannot forge it, because validation happens at Cloudflare’s edge against a secret the client never sees. Cloudflare’s own WAF token-authentication machinery, a separate feature, works on the same principle of an HMAC computed over message and timestamp regions and checked server-side, and cf_clearance is widely understood to carry a signed, timestamped token of similar shape. The precise field layout inside the cf_clearance value is not published. What follows about its binding is inferred from Cloudflare’s documented behaviour and from defensive traffic analysis, not from a public schema, and it is worth stating that distinction out loud rather than inventing field names.

On the question of HttpOnly: Cloudflare’s public cookie documentation specifies SameSite=None; Secure; Partitioned but does not list HttpOnly in that attribute string, and the WAF SameSite-interaction page that enumerates the flags does not name it either. I am not going to assert a flag the primary sources do not state. If you need to know for certain whether a given zone’s cf_clearance carries HttpOnly, read the actual Set-Cookie header off a live response rather than trusting any secondary write-up, including this one.

Here is the crux. A cf_clearance value is not a bearer token in the way an OAuth access token is a bearer token. Cloudflare’s documentation says the cookie is “securely tied to the specific visitor and device it was issued to,” and describes that binding as a deliberate security feature meant to stop the cookie from being transferred and reused on other machines. The cookie proves that this client, in this context, passed a challenge. Move it out of that context and the proof no longer holds.

What constitutes “this context” is the operative question, and the honest answer mixes documented intent with observed behaviour. Three bindings come up consistently.

The first is the IP address. The clearance is validated against the network it was issued from. Replay the same cookie value from a different egress IP, a different proxy, a different datacentre, and the edge treats it as suspect. This is the single most reliable way to kill a copied cookie, and it is why the cookie does not survive being lifted from a residential browser and replayed through a server’s IP. The exact tolerance, whether Cloudflare keys on the exact IP or a network block, and how it treats legitimate IP changes on mobile networks, is not something Cloudflare publishes. What is clear is that a large, abrupt change in source IP invalidates the clearance in practice.

The second is the User-Agent. The clearance was issued to a client presenting a particular UA string, and presenting a different UA on replay breaks it. This one is easy to verify and easy to get wrong: a headless client that obtained the cookie under one UA and then “cleans up” its UA for later requests has just thrown the clearance away.

The third, and the one that turns this from “two header checks” into something harder, is the lower-level fingerprint. Cloudflare scores requests on TLS and HTTP/2 signals that the application layer cannot easily fake, the ClientHello shape that JA3 and JA4 summarise, the HTTP/2 SETTINGS and frame ordering, header order and casing. (Those signals are the subject of the TLS and HTTP/2 fingerprinting post and, more broadly, the JA3-to-JA4 walkthrough.) A cf_clearance cookie presented over a TLS handshake whose fingerprint does not match the one that earned it is a contradiction the edge can see. The cookie says “a Chrome-on-Windows browser passed here”; the handshake says “a Go HTTP client is speaking now.” That mismatch is exactly the signal Cloudflare’s heuristics and machine-learning engines exist to catch.

cf_clearance presented on a later request edge re-checks the context the clearance was issued in IP · User-Agent · TLS / HTTP-2 fingerprint all three match → pass, no challenge any one differs → re-challenge / block the cookie value alone carries no privilege; the matching context does *A copied value travels; the context that makes it valid does not. That asymmetry is the whole defence.*

This is why the cookie is “not portable” in the title’s sense. Portability would mean the value works wherever you paste it. It does not, because the value is only one input to validation and the other inputs are properties of the live connection that issued it. A scraper that solves a challenge in a real browser and then hands the cookie to a fast HTTP client for the bulk work is asking the edge to accept a clearance whose IP, UA, and handshake no longer line up. The mismatch is the tell.

A practical corollary, which Cloudflare itself recommends, is rate limiting on the cookie value. The clearance documentation advises customers to add a rate-limiting rule keyed on the cf_clearance value so that one valid cookie cannot be used to fire an excessive volume of requests. Even if a client keeps IP, UA, and fingerprint perfectly stable, a single clearance driving thousands of requests a minute is itself anomalous, and the operator can cap it. The cookie is a passage, not a licence for unlimited throughput.

Scope: how far one clearance reaches

Scope has two axes. Across a zone, and across sites.

Within the issuing zone, the clearance is broad. Cloudflare describes the cookie as letting a visitor bypass WAF challenges on subsequent requests across the zone, including API requests, subject to the clearance level the customer configured. So passing one challenge on example.com covers later navigations on that same Cloudflare zone without re-challenging at every hop. That is the entire point of the cookie from a user-experience angle: solve once, browse the property. The cookie also records, in effect, what kind of challenge was passed and when, which lets the edge decide whether a given clearance is good enough for a given rule. A clearance earned from a light managed challenge may not satisfy a rule that demands an interactive pass; the clearance level is part of the bookkeeping.

Across sites, scope is where Partitioned rewrites the old behaviour. Before CHIPS, a third-party cf_clearance could in principle be shared across every top-level site that embedded the same Cloudflare-protected third party. With Partitioned, that sharing is gone by design. A clearance obtained while a Cloudflare resource is embedded under site A lives in site A’s partition and is not presented when the same resource is embedded under site B. Clearance does not leak sideways between unrelated top-level contexts. Pass a challenge directly on a site and that clearance is not automatically carried into a different site that embeds the same protected resource, and the reverse holds too.

The Turnstile pre-clearance path has its own scoping rule on top of this. When a Turnstile widget issues a clearance, Cloudflare’s documentation states the cf_clearance cookie is only accepted on domains that match the widget’s configured hostnames, are registered as zones in the account, and have challenges enabled. So the widget operator’s hostname configuration draws the boundary for where a widget-issued clearance counts. It is not a free-floating token good anywhere the widget happens to load.

Lifetime: the Challenge Passage setting

The cookie’s lifetime is a zone setting called Challenge Passage, not a fixed constant. By default the cf_clearance cookie lives for 30 minutes, and Cloudflare recommends a value between 15 and 45 minutes. The operator sets it in the dashboard’s security settings, at the zone level rather than per individual rule, and the value they pick becomes the cookie’s lifetime: a longer passage means fewer repeat challenges and a more permissive window, a shorter one means tighter security and more frequent re-verification.

Challenge Passage (zone-level) 0 min recommended range 15 min 45 min 30 min default + clock-skew grace · + ~1 hour added for XHR to avoid breaking short-lifetime pages *One zone setting drives every clearance cookie's expiry. The default is 30 minutes; operators tune it inside the recommended window.*

There are two wrinkles in how the edge evaluates that lifetime, both documented. When Cloudflare checks a cf_clearance cookie’s age it adds a few extra minutes of grace to account for clock skew between the client and the edge. And for XmlHttpRequests it adds roughly an extra hour to the validation window, specifically to keep short-lifetime pages from breaking their XHR calls mid-session. So the effective lifetime a client experiences is a little longer than the configured number, and noticeably longer for XHR traffic on a zone that set a short passage. Worth knowing if you are reasoning about why a clearance that “should” have expired is still being honoured on background fetches.

One scope note on lifetime: Cloudflare states that the Challenge Passage does not apply to rate-limiting rules. The cookie buys passage past challenge rules; it does not exempt a client from a rate limit. Those are evaluated separately, which is consistent with Cloudflare also recommending a rate limit keyed on the cookie value itself.

The Turnstile pre-clearance door

For a long time the only way to get a cf_clearance cookie was to be handed a challenge page and pass it. That changed in December 2023, when Cloudflare shipped Turnstile pre-clearance. A Turnstile widget configured in pre-clearance mode can issue a cf_clearance cookie for the domain it is embedded on, instead of only handing back a one-time verification token that a server then validates through siteverify. (The token-and-siteverify model is the subject of the Turnstile internals post; pre-clearance is the bridge from that model to the cookie described here.)

The mechanics matter because they preserve every binding property described above while moving issuance to a widget the site embeds itself. Cloudflare’s announcement states the widget-issued clearance is valid only for the time set in the customer’s security settings, the same Challenge Passage value, and that the cookie cannot be shared between users. Clearance cookies issued by a Turnstile widget are applied automatically to the Cloudflare zone the widget is embedded on, with no extra configuration. The headline use case was letting a single-page app’s fetch and XHR calls survive a WAF challenge: when a background request gets challenged, the browser surfaces a Turnstile overlay, and once solved, the widget obtains the cf_clearance cookie and the original request retries and passes. So pre-clearance is a second issuance path to the identical cookie, with the same lifetime control and the same non-shareability, reached through a site-embedded widget rather than a full-page interstitial.

The hostname-matching rule from the scope section is the boundary that keeps this honest. A widget cannot mint clearance for a zone it has no business clearing; the cookie is only accepted where the widget’s configured hostnames, the account’s registered zones, and the challenge-enabled flag all line up.

Where cf_clearance sits next to __cf_bm and the bot score

The two cookies people mix up are cf_clearance and __cf_bm, and the distinction is clean once you see it. __cf_bm is the bot-management cookie. Cloudflare places it on devices that hit sites running Bot Management or Bot Fight Mode, and it carries information tied to the calculation of the bot score, plus a session identifier when Anomaly Detection is enabled. Its job is to smooth the bot score across a single user’s requests so that one anomalous-looking request does not get a real user blocked; it expires after 30 minutes of continuous inactivity. It is a scoring-continuity cookie. (The score itself, the 1-to-99 scale, is the subject of the bot-scoring post.)

cf_clearance is a different animal. It is not about smoothing a score; it is proof that a challenge was passed. Cloudflare’s documentation describes it as required for JavaScript detections, and its presence is what tells the edge to skip the challenge it would otherwise issue. __cf_bm makes scoring stable. cf_clearance records a verdict. A client can carry __cf_bm and still get challenged, because a smoothed score can still land on the wrong side of a rule; carrying a valid cf_clearance, in its proper context, is what actually buys the pass.

The bot score still runs underneath all of this. Passing a challenge and holding a valid clearance does not make a client invisible to the heuristics, machine-learning, and JavaScript-detection engines; those keep evaluating every request. The clearance just answers one specific question, “did this client pass a challenge recently, in this context,” and the answer is only “yes” while the IP, the User-Agent, and the connection fingerprint still match the ones that earned it.

Closing

A copied cf_clearance dies on replay not because Cloudflare detects the copy, but because the cookie was never the whole proof in the first place. The value is a signed, timestamped, edge-validated token capped at 4096 bytes, carried with SameSite=None; Secure; Partitioned, scoped broadly within its issuing zone and walled off from other top-level sites by CHIPS, and alive for whatever the operator set Challenge Passage to, with a few minutes of clock-skew grace and an extra hour for XHR. All of that is the documented half. The undocumented half, the part that makes the cookie refuse to travel, is the set of live-connection properties the edge silently re-checks: the IP it was issued from, the User-Agent that earned it, and the TLS and HTTP/2 fingerprint of the handshake underneath. Change any of those and the token contradicts its own context.

Which is the point worth carrying away. Cloudflare did not build cf_clearance to be a secret you could steal. It built it to be a receipt that only means something while you are standing in the same place you were when it was printed. The string is portable. The standing is not, and the standing is the whole thing.


Sources & further reading

Further reading