Cloudflare
CDN and WAF
Also known as: Cloudflare Bot Management, Cloudflare WAF, Super Bot Fight Mode
Serves challenges and blocks on 403, rate limits on 429, and puts its own four-digit error code in the body rather than on the status line.
Cloudflare is the most common thing sitting between a client and a site, so its responses are the ones most often misread. The single most useful fact about them is structural: Cloudflare's own error numbers are not status codes. Its documentation is explicit that HTTP errors such as 409, 530, 403, and 429 are returned in the HTTP status header, while 1xxx errors appear in the HTML body. Error 1020 is a 403 whose body says 1020. Reading the status line alone throws away the half of the message that tells you which control fired.
The second thing worth knowing is that a challenge and a block look almost identical on the status line, and both are usually 403. The JavaScript challenge and I'm Under Attack Mode moved from 503 to 403 during 2023, which is why older troubleshooting advice keyed on 503 no longer matches what the edge sends. The reliable separator is a header: every interstitial challenge page carries cf-mitigated: challenge, and Cloudflare documents challenge as the only valid value for it. A 403 with that header is a door with a lock on it. A 403 without it is a wall.
Challenges pay out in a cf_clearance cookie rather than a token. Cloudflare describes that cookie as carrying two things at once: challenge clearance, granted when a challenge is solved, and precursor clearance, which is re-evaluated continuously through the session. Clearance is therefore time-bound and behaviour-bound together, so a session can be re-challenged before the cookie expires if the ongoing assessment sours.
How to identify it
- server: cloudflare on the response, plus a cf-ray header carrying the request identifier and the colo that served it.
- cf-mitigated: challenge marks an interstitial challenge page, whatever the status code. Cloudflare documents challenge as the header's only valid value.
- A body referencing /cdn-cgi/challenge-platform/ is the challenge bootstrap script.
- __cf_bm is the bot management cookie and appears on ordinary allowed traffic too, so it marks Cloudflare's presence rather than any verdict.
- A four-digit error number in the body, most often 1020, 1015, 1010, or 1012, names the specific control that fired.
Headers
- cf-mitigated: Set to challenge on every interstitial challenge page, including managed, non-interactive, and interactive types. The cleanest way to tell a challenge from a block.
- cf-ray: Request identifier. Quote it when asking a site owner to look up why a request was stopped, because it is what their Security Events log is searchable by.
- retry-after: Sent with rate limiting responses where the operator configured a period.
Cookies
- __cf_bm: Bot management cookie, set on normal traffic as well as blocked traffic. Presence proves Cloudflare is in front of the origin and nothing more.
- cf_clearance: Issued after a challenge is solved. Holds challenge clearance plus precursor clearance, is tied to the visitor and device it was issued to, and can be invalidated mid-session if precursor scoring decides the session is suspicious.
Status codes
403 Interstitial challenge
Challenge. Documented by the vendor.
A challenge page was served in place of the resource. The request was held, not refused.
What triggers it
- A WAF custom rule, Bot Management rule, or rate limiting rule with a challenge action matched the request.
- Security Level, which defaults to Medium, decided the client's IP reputation warranted a challenge.
- The Browser Integrity Check found a header set it treats as automated.
How to confirm it
- cf-mitigated: challenge is present.
- content-type is text/html even when the request asked for JSON. Cloudflare notes the content type of a challenge is always text/html regardless of the resource type requested.
- The body loads a script under /cdn-cgi/challenge-platform/.
What to do about it
- Detect the header rather than the status code. Cloudflare's own guidance for fetch and XHR callers is to test response.headers.get('cf-mitigated') === 'challenge' and handle that path separately.
- Treat it as a signal that the client cannot complete the site's verification, not as a transient error to retry. Repeating the identical request produces the identical challenge.
- If you have a business relationship with the site, ask the operator for an allow rule. A challenge is a configuration decision on their side and they can see it in their Security Events log by cf-ray.
HTTP/2 403 server: cloudflare cf-mitigated: challenge cf-ray: 8f2a1c4d5e6f7a8b-LHR content-type: text/html; charset=UTF-8 <!DOCTYPE html><html><head><title>Just a moment...</title> <script src="/cdn-cgi/challenge-platform/h/b/orchestrate/chl_page/v1"></script>
403 WAF or firewall block, error 1020
Blocked. Documented by the vendor.
A rule denied the request outright. No challenge is offered and there is nothing for the client to solve.
What triggers it
- A WAF custom rule or managed rule with a block action matched.
- An IP Access Rule or country rule covering the client's address.
- Most 1xxx error codes are carried on a 403, so the body number identifies the specific control.
How to confirm it
- cf-mitigated is absent. This is the difference that matters.
- The body carries a Cloudflare-branded page with an error number and a Ray ID, or a fully custom error page the site owner supplied.
What to do about it
- Read the four-digit number in the body before doing anything else. 1020 is a firewall rule, 1015 is rate limiting, 1010 is a browser signature ban, and 1012 is a client reputation ban. They call for different conversations.
- Capture the Ray ID and the UTC timestamp. Cloudflare's documented resolution path for 1020 is for the visitor to send the site owner a screenshot so the owner can find the event in their Security Events log.
- Do not retry. A rule match is deterministic and identical requests keep matching.
HTTP/2 403 server: cloudflare cf-ray: 8f2a1c4d5e6f7a8b-LHR content-type: text/html; charset=UTF-8 <!DOCTYPE html><html><head><title>Attention Required! | Cloudflare</title> ... <span class="cf-error-code">1020</span> <span>Ray ID: 8f2a1c4d5e6f7a8b</span>
403 Browser signature banned, error 1010
Blocked. Documented by the vendor.
The client's browser signature was rejected. Cloudflare's wording is that the owner of the website has banned your access based on your browser's signature.
What triggers it
- The Browser Integrity Check flagged the request's header set, ordering, or absent headers as non-browser.
- A client presenting a browser User-Agent whose other characteristics do not match that browser.
How to confirm it
- Error code 1010 in the response body.
What to do about it
- Send a coherent client. A header set that claims to be a browser while missing what that browser always sends is the exact pattern this control looks for. An honest, consistent client identity is both more effective and more defensible than an inconsistent disguise.
- Cloudflare's documented resolution is to notify the website owner, because the site owner can turn off the Browser Integrity Check in their security settings. Cloudflare support cannot override a customer's security settings.
403 Client reputation ban, error 1012
Blocked. Documented by the vendor.
Access denied based on activity attributed to the client's address or network rather than to this request.
What triggers it
- Cloudflare's documented common cause is that the site owner forbids access based on malicious activity detected from the visitor's computer or network.
- Shared or recycled address space carrying another party's history.
How to confirm it
- Error code 1012 in the response body.
What to do about it
- This is an address reputation problem, not a request problem. Nothing about the request will change the outcome.
- Cloudflare's documented path is to ask the site owner to allow the address, because Cloudflare cannot override the security settings the site owner has set.
- If you control your egress addressing, check whether the address has history you inherited.
429 Rate limited, error 1015
Rate limited. Documented by the vendor.
The site's rate limiting rules were exceeded. The request shape is acceptable; the pace is not.
What triggers it
- A rate limiting rule counted more requests from the client than its configured threshold allows in its window.
- Cloudflare's own API limits, which are separate from any customer rule: 1,200 requests per five minutes per user, and 200 per second per IP.
How to confirm it
- Error code 1015 in the body of a customer-triggered rate limit.
- retry-after when the operator configured one.
What to do about it
- Honour Retry-After if it is present. If it is not, back off exponentially with jitter rather than retrying on a fixed timer.
- This is the one common Cloudflare response that genuinely rewards waiting. It is a cap rather than a ban, and it clears.
- Lower concurrency before lowering request count. Rate limits usually trip on burst shape rather than on daily totals.
HTTP/2 429 server: cloudflare cf-ray: 8f2a1c4d5e6f7a8b-LHR retry-after: 60
503 Legacy JavaScript challenge
Challenge. Observed behaviour.
The historic status for the JavaScript challenge and I'm Under Attack Mode, replaced by 403 during 2023.
What triggers it
- Older tooling, cached documentation, and archived captures from before the change.
- Non-Cloudflare origin errors that happen to be 503 and are easily mistaken for it.
How to confirm it
- cf-mitigated: challenge, if the response really is a challenge.
- A challenge-platform script reference in the body.
What to do about it
- Do not key new detection logic on 503. Current challenges arrive as 403, and a bare 503 from a Cloudflare-fronted site is now far more likely to be a genuine origin problem.
- Check for cf-mitigated before assuming either reading.
530 Origin unreachable, error 1033 and relatives
Allowed. Documented by the vendor.
Not a bot decision at all. Cloudflare reached the edge but could not reach the origin behind it.
What triggers it
- A misconfigured or down Cloudflare Tunnel, which surfaces as error 1033.
- Origin DNS or hostname problems, which surface as 1016 or 1018.
How to confirm it
- A 1xxx code in the body in the 1016 to 1035 range rather than the block and challenge codes.
What to do about it
- Stop looking at your client. Nothing about the request caused this and no change to it will fix it.
- If you operate the site, this is an origin or tunnel problem. If you do not, wait and try later.
If you run a site behind it
- Prefer a managed challenge over an interactive one. Cloudflare's own recommendation is that managed challenges suit most WAF rules and that other challenge types should not be used unless there is a specific compatibility issue.
- Challenge pages return HTML, which breaks fetch and XHR callers that expect JSON. Cloudflare's answer is Turnstile pre-clearance: verify once on an HTML page, issue cf_clearance, and let API calls through without an interstitial.
- Rate limit on the cf_clearance cookie value as well as on address. Cloudflare recommends this so a single valid clearance cookie cannot be reused to push an excessive volume of requests.
- Watch for challenge loops when challenge rules interact with other Rules features, which is a documented failure mode.
If your traffic is being caught by it
- Read cf-mitigated first, the status code second, and the four-digit body code third. Those three answer, in order: was this a challenge, was it a block, and which control fired.
- Only 429 is worth waiting out. 403 with a rule number behind it will return exactly the same answer an hour later.
- Cloudflare support cannot lift a site owner's block. Every documented resolution path for 1010, 1012, and 1020 routes through the site owner, so that is where to spend the effort.
Documentation
- Cloudflare 1xxx errors
- Detect a Challenge Page response
- Clearance and cf_clearance cookies
- Error 403