Under attack modeNew

Put every page of a zone behind the browser challenge during an application-layer attack, and let it switch itself off when the attack is over.

On this page

Under attack mode puts the browser challenge in front of the whole zone. While it is on, every browser navigation without a valid pass gets the challenge page, whatever the gate says, and every other request without a pass gets a short 403. Clients that can't run JavaScript stop reaching your origin; real visitors wait a moment, click «I am human» once and then browse normally.

It is off by default, it is set for the whole zone (optionally for only some of its names) and it can switch itself off at a time you choose.

When to use it

Turn it on for the duration of an attack that your usual rules can't keep up with:

  • an application-layer (L7) flood: many addresses requesting your pages, too spread out for rate limiting or IP and ASN rules to stop;
  • a scraping wave from rotating proxies or residential addresses.

Set an end time when you turn it on, so it doesn't stay on after the attack.

What visitors see

A browser navigation (a GET or HEAD whose Accept includes text/html, with Sec-Fetch-Mode absent or navigate) without a valid pass gets the challenge page:

  1. It answers 503 with Retry-After: 30, Cache-Control: no-store, X-Robots-Tag: noindex and OpenWAF-Mitigated: challenge. A HEAD gets only the headers.
  2. It says «Checking your browser before accessing www.example.com» (in Spanish, «Comprobando tu navegador antes de acceder a www.example.com»), with the zone's brand, accent colour and the visitor's language.
  3. The browser solves the proof of work while the page says it is checking. By default it is one bit harder than the name's normal challenge, which doubles the work: usually under a second or two, longer on slow phones.
  4. With the interactive step (on by default), the page then asks «Verify you are human» and shows an «I am human» button («Soy humano» in Spanish). The visitor clicks it, taps it or presses Enter or Space on it.
  5. The page shows «Verifying…» and, when the edge accepts it, «Done. Loading the page…»: the edge sets the __waf_pass cookie and the page reloads. While the pass is valid, the visitor browses normally. If the check fails, the page offers a «Try again» button.

Unlike the gate, under attack mode also shows the page on api.* names. Passes earned before you turned it on stay valid until they expire.

Interactive step

On a fast connection the proof of work is done before the visitor notices, so a page that passed by itself would look like no protection at all, and a script that can compute SHA-256 would get through just the same. While under attack, the page therefore asks by default for one click or key press on a button (interactive: true). It is friction for scripts, not proof that a person is there:

  • The button appears once the proof of work is solved, gets the keyboard focus, and its states are announced to screen readers. It uses the zone's brand and accent colour, and follows the light or dark preference, like the Verify widget's interactive state.
  • Only a trusted browser input event on the button counts (a click, tap, or Enter or Space): a click synthesised by a script on the page, such as element.click(), is not trusted and fails. The edge also rejects a click that comes too soon after the button appeared or after the challenge was issued, which stops instant replays: /__waf/verify answers 403 with interaction-required and the page offers «Try again». These attempts count as failed challenges.
  • Browsers that report they are automated (navigator.webdriver) get the same button as everybody else: there is no extra fingerprinting.
  • Passes, their lifetime and their scope don't change; only the way they are earned does.

Effect on automation. Scrapers and flood tools that only fetch your pages, or that run them in a headless browser and solve the proof of work without real input, no longer get a pass. Automation that injects input at the browser or operating-system level (for example Playwright or Puppeteer driving the DevTools protocol) produces trusted events and can pass, as can a person paid to click. The step raises the cost of each new pass, one network at a time; it doesn't prove human intent. Requests that never see the page (APIs, webhooks, exempt paths) are not affected either way.

Set interactive to false to go back to the proof of work alone, so the page passes by itself, for example while you test the mode on a staging name or if your visitors use kiosks where nobody can click. See Turn it on and off.

APIs, mobile apps and webhooks

Any other request without a pass gets 403, never the HTML page: API calls, form posts, fetch and XHR, mobile apps, webhooks, server-to-server calls. The body is JSON when Accept includes json or the request is a fetch (Sec-Fetch-Mode cors, same-origin or no-cors):

HTTP/1.1 403 Forbidden
Content-Type: application/json
Cache-Control: no-store
OpenWAF-Mitigated: challenge
X-Robots-Tag: noindex

{"error":"challenge_required","message":"www.example.com is under attack protection. Open it in a browser to continue."}

Otherwise it is one line of plain text, and a HEAD gets no body. Your clients can recognise it by the OpenWAF-Mitigated: challenge header.

  • Your own web front end. Calls made by pages that already passed the challenge carry the cookie and go through. If www.example.com calls api.example.com, set pass_scope: zone so one pass covers both names, and send the calls with credentials.
  • Mobile apps, partners and server-to-server clients can't hold a pass. Leave their names out with hosts, exempt their paths, or allow their addresses with an IP or ASN allow rule.
  • Webhooks from payment providers and other services: exempt their paths.

GET /__waf/status answers "required": true on a covered name, so a single-page application can reload once to get a pass, as with the gate.

What passes without a challenge

These requests skip under attack mode (the rest of the edge, such as the WAF and rate limits, still applies to them):

RequestNotes
The edge's own paths/__waf/… (challenge, status, access gate) and /.well-known/acme-challenge/….
Paths in exempt_pathsPrefixes matched by whole path segments, ignoring case: /webhooks/stripe matches /webhooks/stripe/events, not /webhooks/stripe2.
A valid __waf_passA pass from the challenge page, earned before or while the mode is on.
IP allow rulesThe zone's and the name's. Country allow rules don't exempt.
ASN allow rulesEvery address of the allowed network.
WAF exceptions with rule_id 0An exception for every rule that matches the host and path.
Verified crawlersSearch engines and other crawlers verified by their published IP ranges, forward-confirmed reverse DNS or Web Bot Auth, whatever the AI Crawl Control mode, so search engines keep indexing your site. The AI Crawl Control policy still applies to them. A request that only claims to be a crawler in its User-Agent is challenged.

Exempt webhooks

Webhooks, payment callbacks and health checks can't solve a challenge. Add their path prefixes to exempt_paths (up to 50; a new list replaces the whole list) when you turn the mode on, or later:

PATCH …/edge
{"challenge": {"under_attack": {"exempt_paths": ["/webhooks/stripe", "/api/payments/callback", "/healthz"]}}}

Exempt paths still go through the WAF, rate limits and your rules. If a provider publishes the addresses it sends from, an IP allow rule for them works too.

Only some names

hosts limits the mode to some names of the zone (up to 50, the apex or names under it); an empty list covers every proxied name. For example, protect www.example.com and shop.example.com and leave api.example.com, used by your mobile app, as it is:

PATCH …/edge
{"challenge": {"under_attack": {"enabled": true, "hosts": ["www.example.com", "shop.example.com"]}}}

The mode is a zone setting: it covers names that have their own challenge settings too, and it can't be set per name with hosts.<name>.challenge.

Difficulty and pass lifetime

  • difficulty: 0 (the default) uses the name's challenge difficulty plus one bit, up to 24, so attackers pay twice the usual work. Set 10–24 for a fixed value; each extra bit doubles the work, for your visitors too.
  • pass_ttl_minutes: 0 (the default) keeps the name's pass lifetime. Set 5–10080 to give shorter passes, for example 60, so that a solved challenge can't be reused for long. It applies to passes earned while the mode is on.

Automatic end

until is the time (RFC 3339, UTC) at which the mode switches itself off: more than one minute ahead and at most 30 days ahead. null keeps it on until you turn it off.

When until passes, within about 30 seconds the control plane turns the mode off as a normal change: it creates a new zone version with the reason «Under attack mode expired (until 2026-10-28T18:00:00Z)», which you see in the zone's history and the audit log, and the edge nodes apply it like any other version. Turning the mode off by hand clears until.

Turn it on and off

Every change is simulated first, needs a reason, creates a new version and can be reverted.

Domain → overview, or Security → Verification → Under attack mode. Choose how long (1 h, 6 h, 24 h or until you turn it off) and, under the advanced options, the names, exempt paths, difficulty and pass lifetime. Ask for a click (I am human) is on by default; switch it off for the proof of work alone. Review the change and apply it with a reason. While it is on, every page of the zone shows a banner with the time left and a button to turn it off.

Fields

challenge.under_attack in the zone's edge settings:

FieldValuesDefaultMeaning
enabledtrue, falsefalseTurns the mode on. false clears until.
untilRFC 3339 UTC time, or nullnullWhen the mode switches itself off: 1 minute to 30 days ahead. Only with enabled: true; null means until you turn it off.
hosts0–50 names of the zone[]Names it covers; empty means every proxied name.
exempt_paths0–50 path prefixes[]Paths never challenged, such as webhooks.
difficulty0, or 10–2400: the name's challenge difficulty plus one, up to 24.
pass_ttl_minutes0, or 5–100800Lifetime of passes earned while the mode is on. 0: the name's pass_ttl_minutes.
interactivetrue, falsetrueAsk for the «I am human» click after the proof of work. false: the page passes by itself. null goes back to true; turning the mode on without it keeps the current value.

Fields merge one by one like the other settings; hosts and exempt_paths replace the whole list, a null field goes back to its default and "under_attack": null turns the mode off and resets every field. hosts.<name>.challenge.under_attack is rejected with 422: use challenge.under_attack.hosts.

Watch the attack

Domain → Security → Verification shows whether the mode is on, until when and whether it asks for a click, and the challenges it issued, solved and failed under the under_attack trigger, next to the other challenge analytics. Solves with the «I am human» click are also counted as interactive. A request that falsely claims to be a crawler is counted under crawler. The same report is openwaf-cloud ctl challenge report example.com, GET /v1/orgs/{org}/zones/{zone}/challenge/analytics and the MCP tool challenge_report.

A high solve rate means mostly real visitors; many issued challenges that are never solved, or many failures with the interactive step, are the attack being held at the edge.

Last updated:

↑↓ to navigate↵ to openesc to close