The OWASP Core Rule Set (CRS) protects a large share of the web, yet many deployments run it with thresholds raised, paranoia levels unset or rule exclusions nobody can explain. CRS is a generic attack detection rule set for WAF engines that speak ModSecurity's SecLang, including ModSecurity v2, libModSecurity v3 and OWASP Coraza, and it is also offered inside many CDN and cloud WAF services. This guide goes beyond our general WAF guide into how CRS itself works: rule file structure, anomaly scoring, paranoia levels, rule exclusions, the configuration baseline in crs-setup.conf, who should be allowed to change it and how to log and measure it. It is for engineers who run or tune CRS and for security leads who own WAF change control.
Key takeaways
- Keep the default thresholds. CRS defaults are an inbound anomaly threshold of 5 and an outbound threshold of 4; the CRS documentation warns that raising them permanently lets a substantial number of attacks bypass the rule set.
- Paranoia level and threshold are separate controls. The paranoia level decides how many rules run; the threshold decides how many matches are needed to block. Do not trade one for the other.
- Tune with exclusions, never by editing rules. Use configure-time or runtime rule exclusions, as narrow as possible, placed in the correct file so they survive upgrades.
- Stay on a supported release. The project supports the two latest point releases and a v4.25.x LTS line until Q3 2027; the v3.3.x branch reached end of life in Q3 2026.
- Treat WAF configuration as privileged access. Whoever can add an exclusion or set the sampling percentage to 0 can switch protection off, so change rights and API tokens need the same control as production admin access.
What CRS is: engines, versions and support
CRS is an OWASP project that publishes rules, not a WAF engine. It runs on engines compatible with a subset of SecLang, and the order of inclusion is always the engine configuration (modsecurity.conf or coraza.conf), then crs-setup.conf, then the rules/*.conf files.
| Engine or platform | CRS compatibility (per CRS documentation) | Notes |
|---|---|---|
| ModSecurity v2 (Apache) | Reference implementation; passes 100% of CRS unit tests | Project now under the OWASP Foundation (transferred January 2024) |
| ModSecurity v3 (libModSecurity, mainly Nginx) | Fails 2 to 4% of CRS unit tests due to bugs and implementation gaps | Used in production with Nginx; CRS recommends v2 with Apache where possible |
| OWASP Coraza (Go) | Passes 100% of the CRS v4 test suite | Runs on Caddy and Traefik, with an HAProxy SPOA connector |
| Cloud and CDN WAFs | Usually a subset of CRS rules and features, often transposed into the provider's own engine | Check which CRS version is offered and whether paranoia level and thresholds can be set |
Release status as checked in the CRS repository on 27 October 2026: the latest release is v4.30.0 (2 October 2026), the security policy supports the two latest point releases plus the v4.25.x LTS line until Q3 2027, and releases are GPG signed. The LTS line receives only security fixes, critical regression fixes and critical false positive fixes. Note one naming trap the CRS documentation calls out: a cloud provider's managed rule group named "core rule set" is not necessarily based on OWASP CRS. See the Web Application Firewall Guide for WAF deployment models and the wider rule set choice.
How the rule set is organised
Rules live in numbered files; the number is also the rule ID prefix (for example rule 920160 is in the protocol enforcement file). CRS reserves IDs 900,000 to 999,999; IDs 1 to 99,999 are reserved for local use, which is where your own exclusions and custom rules belong.
| File range | Purpose | Examples |
|---|---|---|
| REQUEST-900, RESPONSE-999 (.example) | Your exclusion rules before and after CRS | Application-specific exclusions |
| REQUEST-901, 905 | Initialisation and common exceptions | Default values, sampling (rules 901410, 901450) |
| REQUEST-911, 913, 920, 921, 922 | Method enforcement, scanner detection, protocol enforcement, protocol attacks, multipart attacks | Non-numeric Content-Length (920160), charset allow-list (920480) |
| REQUEST-930 to 934 | Local and remote file inclusion, remote code execution, PHP, generic attacks | AI coding assistant directories such as .claude/ and .cursor/ in the restricted paths data used by the LFI rules |
| REQUEST-941 to 944 | XSS, SQL injection, session fixation, Java attacks | More than 50 rules cover SQL injection |
| REQUEST-949 | Inbound blocking evaluation | Compares the inbound anomaly score with the threshold |
| RESPONSE-950 to 956 | Data leakage (SQL, Java, PHP, IIS, Ruby) and web shells | Error messages and web shell output in responses |
| RESPONSE-959, 980 | Outbound blocking evaluation and correlation | Outbound threshold check |
Rules carry tags such as attack-protocol, paranoia-level/1 and capec identifiers, which is what makes tag-based exclusions and reporting possible.
How decisions are made: anomaly scoring and paranoia levels
In the default anomaly scoring mode, detection is decoupled from blocking. Each matching rule adds to a transaction score (a critical match adds 5 by default); after all request rules run, rule file 949 blocks the request if the inbound score meets or exceeds the inbound threshold; after response rules, file 959 does the same for the outbound score, stopping the response even though the backend has already processed the request. Self-contained mode, the CRS 2 behaviour, acts on the first match and logs only the first threat.
| Paranoia level | CRS project description | Typical fit |
|---|---|---|
| PL 1 | Baseline security with minimal false positives; CRS for everybody on the internet | Brochure sites, low-risk services |
| PL 2 | Adequate when real user data is involved; expect false positives to tune | Online shops, applications with personal data |
| PL 3 | Online banking level; false positives accepted and expected | Financial and sensitive data |
| PL 4 | For "crown jewels", at your own risk, with many false positives | Narrow, high-value admin endpoints |
The CRS documentation suggests an internal policy mapping data risk class to a minimum paranoia level (no personal data to PL 1, personal data to PL 2, sensitive financial data to PL 3). To raise the level on a tuned system without reducing protection, set the detection paranoia level (tx.detection_paranoia_level) one step above the blocking paranoia level (tx.blocking_paranoia_level): higher-level rules then log without contributing to blocking, you tune the new false positives, and only then raise the blocking level.
How identity and access work in a CRS deployment
CRS has no user accounts of its own: it is configuration files loaded by an engine, or a rule set exposed through a cloud console. Access control therefore lives in three places, and each needs an owner.
- Configuration source: crs-setup.conf, the 900 and 999 exclusion files and any plugins, ideally in a version-controlled repository with mandatory review. Anyone with merge rights can disable protection.
- Engine host or ingress: the web server, reverse proxy or Kubernetes ingress controller that loads the rules. Shell or cluster-admin access here bypasses repository review entirely.
- Managed console or API: where CRS runs inside a CDN or cloud WAF, the provider's IAM roles decide who can change paranoia level, thresholds and exclusions.
CRS also inspects identity-related traffic. The session fixation rules (file 943) look for cookie values being set in HTML and session ID parameter names arriving with an off-domain or missing referer, restricted header lists cover method override headers such as X-HTTP-Method-Override, and the restricted files data now blocks requests for AI coding assistant directories and configuration files that can hold API keys. None of this replaces authentication controls in the application.
Admin roles and privileged access
| Role | Should be able to | Should not be able to |
|---|---|---|
| WAF rule author | Propose exclusions and custom rules in the local ID range through a pull request | Merge their own changes or edit CRS rule files directly |
| WAF approver (security) | Approve exclusions, threshold and paranoia level changes | Deploy outside the pipeline |
| Platform operator | Deploy approved releases and roll back | Change thresholds or sampling outside change control |
| Application owner | Request exclusions with evidence (rule ID, parameter, URI) | Approve exclusions for their own application |
| Break-glass | Set tx.sampling_percentage to 0 or switch the engine to detection only during an outage | Leave the change in place without a time limit and review |
Map these roles to least privilege (NIST SP 800-53 AC-6) and configuration change control (CM-3). See the Privileged Access Management Guide for how to put admin consoles and hosts behind just-in-time access.
Integrations, API tokens and service accounts
Modern CRS deployments are driven by automation: CI pipelines that run the CRS regression tests, infrastructure as code that pushes WAF policies to a CDN, and log shippers that send audit logs to a SIEM. Each of these holds a non-human credential.
- Deployment tokens: scope CDN or cloud WAF API tokens to the WAF policy resources only, store them in a secrets manager, and rotate them. A token that can edit WAF policy can also delete it.
- Pipeline identities: prefer short-lived workload identity federation for the pipeline that applies WAF changes over static keys. See the Secrets Management Guide.
- Log shipping accounts: audit logs contain request bodies, cookies and sometimes credentials. The log shipper's account needs write access to the log store, not read access to everything else.
- Release verification: verify the GPG signature of each CRS release before deployment so a tampered rule set cannot enter the pipeline.
WAF providers are themselves a target. In the Imperva Breach 2019, an administrative AWS API key on an exposed internal instance gave an attacker access to a database snapshot that included Cloud WAF customers' API keys and SSL certificates for a subset of accounts. Rotate any credentials you have given a WAF provider when the provider reports an incident.
Rolling out and tuning CRS
- Baseline the engine: load the engine's recommended configuration, then crs-setup.conf, then the rules, on a supported engine version.
- Choose the start mode: either use sampling (rule 900400, tx.sampling_percentage) to apply CRS to 1% of traffic and increase in steps to 100%, or run in blocking mode from the start with very high anomaly thresholds and lower them iteratively, the method CRS documentation describes.
- Start at PL 1 unless a risk class mapping requires more, and set the detection paranoia level one step higher to see what is coming.
- Tune false positives from logs: identify rule ID, variable and URI, then write the narrowest exclusion. Prefer removing one variable from one rule for one location (ctl:ruleRemoveTargetById) over removing a rule globally (SecRuleRemoveById). Use the 1 to 99,999 ID range.
- Place exclusions correctly: configure-time exclusions must come after CRS is included (the RESPONSE-999 file); runtime exclusions go before CRS (the REQUEST-900 file). Misplaced exclusions silently do nothing.
- Use plugins for application exclusions: CRS v4 ships rule exclusion plugins for common applications, loaded before and after CRS, and they can be enabled per host or application.
- Lower thresholds to defaults (5 inbound, 4 outbound) and keep them there.
- Test before each upgrade against recorded traffic or the CRS test suite, then upgrade in a canary.
Rollback: the fastest documented rollback is setting tx.sampling_percentage to 0, which bypasses the WAF for every request (and any other rule sets loaded in the same engine). Use it only as a time-limited break-glass action with an incident ticket.
Hardening baseline: crs-setup.conf settings that matter
| Setting (rule ID) | Variable | Baseline |
|---|---|---|
| Paranoia level (900000) | tx.blocking_paranoia_level, tx.detection_paranoia_level | Set explicitly per application risk class |
| Anomaly thresholds (900110) | tx.inbound_anomaly_score_threshold, tx.outbound_anomaly_score_threshold | 5 and 4; raise only temporarily during tuning |
| Early blocking (900120) | tx.early_blocking | Off by default; when enabled, scores are also evaluated at the end of phases 1 and 3 so blocking happens earlier |
| Allowed methods (900200) | tx.allowed_methods | Default GET HEAD POST OPTIONS; add PUT, PATCH or DELETE only for APIs that need them |
| Allowed content types (900220) | tx.allowed_request_content_type | Restrict to what the application accepts |
| Allowed HTTP versions (900230) | tx.allowed_http_versions | Remove versions you do not serve |
| Restricted extensions and headers (900240, 900250) | tx.restricted_extensions, tx.restricted_headers_basic | Keep defaults; add backup and config extensions specific to your stack |
| Argument and upload limits (900300 to 900350) | tx.max_num_args, tx.arg_length, tx.max_file_size and others | Set from real application limits |
| Sampling (900400) | tx.sampling_percentage | 100 in steady state |
Engine settings such as SecRuleEngine, SecRequestBodyAccess and SecAuditEngine are not set by CRS; they belong to the engine configuration and must be hardened there. Without request body access, CRS cannot inspect POST bodies at all.
Logging and detection
By default CRS logs to the web server error log and writes detailed transactions to the engine audit log. Ship both to the SIEM, alert on configuration changes, and protect the audit log because it holds request payloads.
- Blocked requests by rule ID and paranoia level: spikes in one rule usually mean a new false positive or a targeted attack.
- Rule 901450 entries: these show requests that bypassed CRS because of sampling; any in steady state mean sampling is not at 100.
- Configuration drift: alert when thresholds, paranoia level or sampling differ from the approved baseline.
- Exclusion inventory: number of active exclusions, their age and owner.
Metrics: percentage of in-scope applications at their target paranoia level; percentage running default thresholds; false positive rate per thousand requests after tuning; median time to approve an exclusion request; days behind the latest supported CRS release; exclusions older than 12 months without review.
In framework terms, CRS supports NIST SP 800-53 SC-7 Boundary Protection, SI-10 Information Input Validation and SI-4 System Monitoring; NIST CSF 2.0 PR.IR-01 and DE.CM-01; and the MITRE D3FEND technique Inbound Traffic Filtering. It is a mitigation layer against MITRE ATT&CK T1190 Exploit Public-Facing Application, not a fix for the vulnerability. For time-bound protection while a patch is pending, see the Virtual Patching and Compensating Controls Guide.
Common misconfigurations
- Permanently raised thresholds: a threshold of 10 or more means attack categories covered by a single rule can never block.
- Editing CRS rule files: changes are lost on upgrade and cannot be audited; use exclusions and the local ID range instead.
- Global rule removal for one false positive: removing an SQL injection rule everywhere because one search field triggers it.
- Exclusions in the wrong file: configure-time directives placed before the CRS include have no effect.
- Running end-of-life releases: v3.3.x and older are no longer supported, and recent releases have fixed bypasses (v4.30.0 included fixes for path-based command injection and charset allow-list bypasses).
- Assuming a managed "CRS" is full CRS: many integrations offer a subset of rules and do not expose paranoia levels or thresholds.
- Detection only forever: leaving the engine in detection-only mode after the pilot provides logging but no protection.
Practitioner checklist
- Confirm every CRS deployment is on a supported release or the v4.25.x LTS line, and verify release signatures.
- Record the engine and version for each deployment and note known CRS compatibility gaps.
- Set blocking and detection paranoia levels per application from a written risk class mapping.
- Restore default anomaly thresholds (5 inbound, 4 outbound) after tuning, and alert on any change.
- Keep all exclusions in version control, in the correct file, in the local ID range, each with an owner and ticket.
- Restrict who can merge WAF changes, deploy them and use break-glass sampling or detection-only switches.
- Scope, vault and rotate API tokens used by pipelines and log shippers.
- Ship error and audit logs to the SIEM and protect them as sensitive data.
- Run the CRS test suite or recorded traffic before upgrades and deploy through a canary.
- Review the exclusion inventory every quarter and remove stale exclusions.
Standards and references
- OWASP CRS: Core Rule Set repository
- OWASP CRS: Security policy and supported versions
- OWASP CRS: Documentation source (anomaly scoring, paranoia levels, false positives and tuning)
- NIST: SP 800-53 Rev. 5
- NIST: Cybersecurity Framework 2.0
- MITRE D3FEND: Inbound Traffic Filtering
- MITRE ATT&CK: Exploit Public-Facing Application
Related NHI Mgmt Group resources: Web Application Firewall Guide · Virtual Patching and Compensating Controls Guide · WAF and WAAP Buyer's Guide · SQL Injection Prevention Guide · Web Shell Response Playbook