mod_security setup and domestication on your Apache server
If you’ve ever worked with a Web Application Firewall (WAF), you probably know how challenging it can be to deploy one in a real-world production environment. This is especially true when running it in blocking mode. Once enabled, a WAF can unexpectedly interfere with legitimate requests, causing certain features of the underlying application to stop working because requests are blocked and users receive 4xx HTTP status codes.
ModSecurity is a WAF module which integrates with Apache HTTP Server and analyses incoming traffic using predefined security rules. It is commonly deployed with the OWASP Core Rule Set (CRS), which provides protection against common web application attacks, this is how I installed it on Fedora Linux Server for my Nextcloud
Instalation and basic config
dnf install httpd mod_security mod_security_crs
Restart Apache to load the module :
systemctl restart httpd
Check if is loaded:
httpd -M | grep security
Expected:
security2_module (shared)
The main ModSecurity configuration file is typically located at:
/etc/httpd/conf.d/mod_security.conf
Edit /etc/httpd/conf.d/mod_security.conf and set:
SecRuleEngine DetectionOnly
In this mode, ModSecurity evaluates incoming HTTP requests and logs potential security violations, but does not execute disruptive actions defined by WAF rules.
SecAuditEngine RelevantOnly
SecAuditLog /var/log/httpd/modsec_audit.log
SecAuditLogParts ABIJDEFHZ
And here is the important thing: DetectionOnly does not disable ModSecurity. Security rules are still evaluated, and suspicious requests are logged. This makes it possible to understand how the WAF behaves with real production traffic before enforcing blocking rules.
Monitoring:
Look at files like this:
- /var/log/httpd/modsec_audit.log (Detailed WAF security events)
- /var/log/httpd/error_log (ModSecurity warnings and Apache errors)
- /var/log/httpd/access_log (HTTP requests and response status codes)
or monitor all events i you have more files like me:
tail -f /var/log/httpd/*
This is really helpful grep which you can use to find the Rule Ids that were triggered:
grep -oE '\[id "[0-9]+"\]' /var/log/httpd/* | sort | uniq -c | sort -nr
For Fedora installations using OWASP CRS 4, exclusions can typically be placed in:
/etc/httpd/modsecurity.d/activated_rules/REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf
For example, suppose the /api/editor/save endpoint accepts HTML content and triggers false positives for rule 941100. Instead of disabling the rule globally, exclude a specific parameter for that endpoint:
SecRule REQUEST_URI "@streq /api/editor/save" "id:10001,phase:1,pass,nolog,t:none,ctl:ruleRemoveTargetById=941100;ARGS:content"
It is also possible to disable an entire rule for a particular endpoint:
SecRule REQUEST_URI "@streq /api/legacy" "id:10002,phase:1,pass,nolog,t:none,ctl:ruleRemoveById=942100"
Validate and reload Apache:
apachectl configtest
systemctl reload httpd
Best practices for exclusions:
- Prefer parameter-level exclusions over disabling entire rules.
- Restrict exclusions to specific endpoints whenever possible.
- Assign unique IDs to custom rules.
- Document the reason for each exclusion.
Change the mode
Finally, you can switch to active blocking mode; it is simply as simple as changing:
SecRuleEngine DetectionOnly
to
SecRuleEngine On
modsecurity can work on a side server in proxy mode with modproxy on Apache.
A long time ago, I implemented this for the website szkla.com. The architecture diagram:
+—————+ +——————————+ +———————+ | | | Apache WAF Server | | | | Client | —> | ModSecurity | —> | Backend Server | | | | mod_proxy | | | +—————+ +—————————+ +———————–+ | | Blocked v HTTP 403