Set a site's security level in site.json
Clarification By design
The "security" section of a site's settings (#/security.json, or "security" in #/site.json, inherited from the app) decides which visitors must pass a Cloudflare Turnstile check before the site answers: level normal (nobody), elevated (probes, flagged IPs and low-trust countries), high (everyone except high-trust countries) or underAttack (everyone). The check runs before the page cache. From high upwards search engines are stopped too, so set "until" when you raise the level, and "exclude" paths that machines call, such as webhooks.
Applies to: site.jsonsecurity.jsonSecurityPublished sites
What you'll see
A site is being scraped, flooded or probed, and you want visitors to prove they are human before they get anything — or a site suddenly shows a “Checking your browser” page to some visitors, and a script or webhook that calls it gets 403.
What's actually happening
The level is set per site, in #/security.json or in the security section of #/site.json. A site built on an app inherits the app's security settings key by key and only needs what differs (see Configure site settings and inherit them from the app). In #/site.json:
"security": {
"level": "elevated",
"clearanceMinutes": 60,
"until": "2026-11-01T12:00",
"exclude": [ "/api/webhooks/*", "/health" ],
"abuseBlocking": true
}| level | Who must pass the check | Search engines |
|---|---|---|
normal | Nobody. The firewall and automatic blocking work as always. | indexed |
elevated | Requests for flagged URLs that the site does not have (probes such as wp-login.php), IP addresses that are already flagged, and countries with low trust. | indexed |
high | Everyone except visitors from countries with high trust. | stopped |
underAttack | Everyone. | stopped |
- What the visitor gets. A page view (a GET or HEAD navigation) gets a challenge page with status 403. Everything else — API calls,
fetch, images, files, POST — gets403with{"ResultCode":-213,"Message":"Verification required."}. Both areno-store. - A passed check lasts
clearanceMinutes(default 60, at most one week) for that site, in a cookie. - The check runs before the cache, so cached pages are protected too.
until(ISO 8601; without a time zone, the server's local time) ends the site's own level. Afterwards the platform default applies. A value that cannot be read is ignored, and the level then has no end.excludelists paths, relative to the site, that never get the check. A trailing*means everything below; otherwise the path must match exactly. Case does not matter. A site'sexcludereplaces the app's list as a whole, so repeat the app's paths the site still needs.- The platform default applies when neither the site nor its app sets a level. It is
normalorelevated, never higher. The site's ownlevelalways wins, over the app's and the platform's, including"normal", which is how a site opts out. - An unknown level (a typo) counts as not set.
abuseBlocking(defaulttrue):falselets visitors through whose address is blocked only because the IP data provider marks it as abusive. All other firewall rules still apply.
What to do
- Use
elevatedas the everyday level for a public site. It challenges only what already looks suspicious, and search engines are not affected. - Raise to
highorunderAttackonly during an attack, and always withuntil. A forgottenunderAttackkeeps search engines out for as long as it stands. - Put every path that a machine calls in
excludebefore you raise the level: webhooks, payment callbacks, uptime checks. They cannot pass Turnstile. - Client code that calls your API should treat
403withResultCode -213as “reload the page”: the page view gets the challenge, and the clearance cookie then covers the API calls. - Verify with
curl -Ifrom a machine without the clearance cookie: an excluded path must not answer403.