A web application firewall (WAF) inspects HTTP requests and responses and blocks those that look like attacks such as SQL injection, cross-site scripting or path traversal. On self-managed Linux servers the most common open-source combination is the ModSecurity engine with the OWASP Core Rule Set (CRS). It is a useful extra layer, but only if it is tuned: an untuned WAF either blocks real customers or gets switched off.
This guide explains how CRS decides what to block, how to roll it out safely, and which versions are current as of October 2026.
The pieces and their versions
- ModSecurity is the engine, now maintained under OWASP on GitHub. Version 2 is an Apache module; version 3 (libmodsecurity) is a standalone library used with connectors such as ModSecurity-nginx. Both lines received releases on 29 September 2026 (2.9.15 and 3.0.17).
- CRS is the rule set. The CRS project lists 4.30.0 as the latest release and 4.25.2 as the long-term support (LTS) release, both from 2 October 2026, and states that CRS 3.3.x has reached end of life and that organisations still running it should migrate. CRS also runs on the Coraza engine.
Distribution packages lag behind. Debian 12 and Debian 13 package ModSecurity 2.9.x for Apache (libapache2-mod-security2), libmodsecurity 3.0.x with an Nginx module, and CRS from the 3.3 line (modsecurity-crs). That is fine for learning and for a first deployment, but plan to move to CRS 4: the CRS project's migration series says CRS 3.3 receives no false-positive fixes or new detections. If you combine an upstream CRS release with distribution engines, read its release notes first: the July 2026 release notes ask for ModSecurity 2.9.14 or libmodsecurity 3.0.16, newer than the 2.9.11 and 3.0.14 that Debian 13 ships.
How CRS decides: anomaly scoring
CRS does not block on the first matching rule. According to its anomaly scoring documentation, each matching rule adds to a score, and once all request rules have run the request is blocked if its score reaches the inbound threshold; responses are scored and checked against an outbound threshold in the same way.
- Most detected attacks carry a score of 5 (critical).
- The default thresholds are 5 for requests and 4 for responses, so one critical match blocks a request.
- The CRS team warns that raising thresholds above the defaults lets a substantial number of attacks through.
Paranoia levels
The paranoia level (PL) decides how many rules are active:
| Level | Intended for |
|---|---|
| PL 1 (default) | Baseline protection for any site, with rare false positives |
| PL 2 | Sites handling real user data, such as an online shop; expect false positives to tune |
| PL 3 | Online-banking level; many false positives |
| PL 4 | Very sensitive services; a large number of false positives and weeks of tuning |
The docs suggest mapping risk to level: no personal data, PL 1; personal data such as names and addresses, PL 2; sensitive financial data, PL 3. Start at PL 1 regardless and move up once it runs cleanly.
A cautious rollout on Debian with Apache
sudo apt update
sudo apt install libapache2-mod-security2 modsecurity-crs
sudo a2enmod security2
sudo cp /etc/modsecurity/modsecurity.conf-recommended /etc/modsecurity/modsecurity.conf
sudo apachectl configtest && sudo systemctl restart apache2Debian's module configuration includes every *.conf in /etc/modsecurity and loads CRS from /usr/share/modsecurity-crs; the CRS settings file is /etc/modsecurity/crs/crs-setup.conf. The recommended engine configuration starts with SecRuleEngine DetectionOnly: rules run and are logged, but nothing is blocked.
Run in DetectionOnly long enough to see normal traffic, including logins, checkouts, file uploads, admin pages and API clients. Review what would have been blocked; the audit log location is set by SecAuditLog in modsecurity.conf. Then switch to SecRuleEngine On. The CRS docs also describe an alternative: start in blocking mode with very high thresholds (even 10000) and lower them step by step to the defaults.
The paranoia level and thresholds live in crs-setup.conf, in rules 900000 and 900110. In CRS 4 the paranoia variable is tx.blocking_paranoia_level; CRS 3.3 used tx.paranoia_level.
Tuning false positives
A false positive is a legitimate request that scores high, for example a CMS editor saving HTML, or a password field containing characters that look like SQL. Fix it with a rule exclusion as narrow as possible, as the tuning guide describes:
SecRuleRemoveByIddisables a rule everywhere (coarse; use sparingly).SecRuleUpdateTargetByIdstops a rule inspecting one parameter or cookie everywhere.ctl:ruleRemoveByIdandctl:ruleRemoveTargetByIdinside your own rule do the same only for a given path or condition (preferred).
For example, to stop rule 941320 inspecting a parameter named wp_post:
SecRuleUpdateTargetById 941320 "!ARGS:wp_post"Exclusions belong in the files CRS provides for them (REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf and RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf, created from their .example versions), not in edits to the rule files, which updates overwrite. CRS 4 replaced the old application exclusion packages with plugins; if you used an exclusion package under CRS 3, install the matching plugin.
Pitfalls
- Raising the anomaly threshold to silence false positives instead of writing exclusions.
- Jumping straight to PL 3 or 4 in blocking mode.
- Leaving DetectionOnly on for months and never reading the logs.
- Treating the WAF as a substitute for patching the application. It reduces risk; it does not fix vulnerable code.
What you should do
- Start with PL 1 and default thresholds, in DetectionOnly or with very high thresholds.
- Review logs against real traffic and write narrow exclusions in the exclusion files.
- Switch to blocking, then consider PL 2 if you handle personal data.
- Plan the move from CRS 3.3 to a CRS 4 release, checking engine version requirements in the release notes.
- Keep the application, the engine and the rule set updated.
Sources
- coreruleset.org
- coreruleset.org/docs/2-how-crs-works/2-1-anomaly_scoring
- coreruleset.org/docs/2-how-crs-works/2-2-paranoia_levels
- coreruleset.org/docs/2-how-crs-works/2-3-false-positives-and-tuning
- coreruleset.org/docs/1-getting-started/1-1-crs-installation
- coreruleset.org/20260330/migrating-crs-3-to-4-part-1-overview
- coreruleset.org/20260702/crs-versions-4.28.0-4.25.1-lts-and-3.3.10-released
- coreruleset.org/20261002/crs-versions-4.30.0-and-4.25.2-lts-released
- github.com/coreruleset/coreruleset/blob/v4.25.2/crs-setup.conf.example
- github.com/owasp-modsecurity/ModSecurity
- github.com/owasp-modsecurity/ModSecurity/releases
- github.com/owasp-modsecurity/ModSecurity-nginx
- packages.debian.org/trixie/libapache2-mod-security2
- packages.debian.org/trixie/modsecurity-crs
- packages.debian.org/trixie/libmodsecurity3t64
- packages.debian.org/bookworm/modsecurity-crs
- sources.debian.org/src/modsecurity-apache/2.9.11-1%2Bdeb13u1/debian/security2.conf
Comments
0 total · 0 threads