Security Level — Challenge Sensitivity
Cloudflare Security Level determines how aggressively to challenge visitors based on their IP reputation score — offering a sliding scale from essentially trust-everything to challenge-everything.
What You Will Learn
You will learn how Security Level interacts with IP reputation scores, how to tune it for different parts of your site, and how to override it for specific paths using page rules or WAF expressions.
Why It Matters
A single security level for the entire site is rarely optimal. Login pages and admin panels benefit from higher sensitivity, while static content and public APIs should have lower sensitivity to avoid false positives.
Real-World Use Case
A community forum uses Security Level set to High for all /login/* and /register/* paths using a page rule, while the public content pages remain at Medium. This keeps spammers out of signup flows without challenging legitimate readers browsing forum threads.
How Security Level Works
Cloudflare assigns every IP address a threat score based on factors including known attack sources, behaviour patterns, and global threat intelligence. The Security Level setting maps threat score ranges to challenge thresholds.
flowchart LR
A[Visitor IP] --> B{Check Threat Score}
B -->|Score 0-49| C["Low / No Challenge"]
B -->|Score 50-69| D[Medium Challenge]
B -->|Score 70-99| E[High Challenge]
B -->|Score 100+| F[I'm Under Attack!]
C --> G[Origin]
D --> G
E -->|JS Challenge| G
F -->|5-second wait| G
Security Level Tiers
| Level | Threat Score Threshold | Behavior |
|---|---|---|
| Essentially Off | Ignores threat scores | No IP-based challenges |
| Low | Scores above 80 | Challenges highly suspicious IPs |
| Medium (default) | Scores above 60 | Balances protection and friction |
| High | Scores above 40 | More aggressive challenges |
| I'm Under Attack! | Scores above 20 | JS challenge + 5s delay |
Setting Security Level via API
curl -X PATCH "https://api.cloudflare.com/client/v4/zones/ZONE_ID/settings/security_level" \
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
-H "Content-Type: application/json" \
--data '{"value": "high"}'
Expected output:
{
"result": {
"id": "security_level",
"value": "high",
"modified_on": "2026-06-23T10:00:00Z"
},
"success": true
}
Page Rule: Different Levels per Path
Apply different security levels to different URL patterns using page rules:
curl -X POST "https://api.cloudflare.com/client/v4/zones/ZONE_ID/pagerules" \
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
-H "Content-Type: application/json" \
--data '{
"targets": [
{"target": "url", "constraint": {"operator": "matches", "value": "*example.com/admin*"}}
],
"actions": [
{"id": "security_level", "value": "high"}
],
"status": "active"
}'
Expected output:
{
"result": {
"id": "page_rule_id",
"status": "active",
"targets": [{"target": "url", "constraint": {"operator": "matches", "value": "*example.com/admin*"}}],
"actions": [{"id": "security_level", "value": "high"}]
},
"success": true
}
Python: Analyse Threat Score Distribution
import os
import requests
ZONE_ID = os.environ["CLOUDFLARE_ZONE_ID"]
TOKEN = os.environ["CLOUDFLARE_API_TOKEN"]
URL = f"https://api.cloudflare.com/client/v4/zones/{ZONE_ID}/analytics/dashboard"
headers = {"Authorization": f"Bearer {TOKEN}", "Content-Type": "application/json"}
resp = requests.get(URL, headers=headers)
data = resp.json()
# Extract threat score buckets from analytics (simplified)
col = data["result"]["totals"].get("threats", {})
print(f"Threats blocked: {col.get('blocked', 0)}")
print(f"Threats challenged: {col.get('challenged', 0)}")
print(f"Threats passed: {col.get('passed', 0)}")
Expected output:
Threats blocked: 2341
Threats challenged: 892
Threats passed: 45
Node.js: Toggle Security Level on Schedule
const fetch = require("node-fetch");
const ZONE_ID = process.env.CLOUDFLARE_ZONE_ID;
const TOKEN = process.env.CLOUDFLARE_API_TOKEN;
const HOUR = new Date().getHours();
const isBusinessHours = HOUR >= 8 && HOUR <= 20;
const level = isBusinessHours ? "medium" : "high";
fetch(
`https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/settings/security_level`,
{
method: "PATCH",
headers: {
Authorization: `Bearer ${TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ value: level }),
}
)
.then((r) => r.json())
.then((d) => console.log("Set security_level to", d.result.value));
Expected output:
Set security_level to medium
Common Mistakes
| Mistake | Consequence |
|---|---|
| Setting to I'm Under Attack! permanently | Poor user experience for all visitors |
| Using Low on login pages | Allows brute force from suspicious IPs |
| Not creating overrides for API paths | API clients hit JS challenge walls |
| Forgetting that page rules cost plan credits | Page rules consume your plan quota |
| Not reviewing threat analytics | Blind to whether level is effective |
Practice Questions
- What is the default Security Level and what threat score threshold does it use?
- How can you use page rules to apply a different Security Level to your checkout page?
- Why is I'm Under Attack! unsuitable as a permanent setting?
Challenge
Create a Cloudflare Worker or cron job that checks the current time and automatically switches Security Level from Medium (daytime) to High (nighttime) for a zone using the API. Log each change to a file.
Real-World Task
Your site has an API endpoint at /api/v1/* that receives requests from mobile apps. Apply a Security Level override for this path so it uses Essentially Off while keeping the rest of the site at Medium. Test that API clients are not challenged while web visitors still have protection.
FAQ
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro — security-first tools for the modern web.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro