If Apache-level IP blocking and basic hardening are the locks on your doors, mod_security is the alarm system that watches everything happening inside. I install it on nearly every production server I manage, because it catches entire categories of attacks — SQL injection, XSS, malicious file uploads — before they ever reach your application code. Here’s my complete process for setting it up.
What Is mod_security?
mod_security is an open-source Web Application Firewall (WAF) module for Apache. It inspects incoming HTTP requests (and outgoing responses) against a set of rules, and can block, log, or flag anything that matches a known attack pattern. Most people pair it with the OWASP Core Rule Set (CRS), a community-maintained ruleset covering the most common web application attacks.
Why I Install mod_security on Every Server
- Blocks common attack patterns like SQL injection and cross-site scripting before they hit your application
- Provides detailed audit logging for forensic analysis after an incident
- Virtual patching — lets you block a known vulnerability in your application before you’ve had time to actually fix the code
- Works alongside application-level security, not as a replacement for it
Prerequisites
- Root or sudo access
- Apache 2.4+ installed
- A staging environment (mod_security in blocking mode can break legitimate functionality if misconfigured, so I never enable it in blocking mode directly on production without testing)
Step 1: Install mod_security
On Debian/Ubuntu:
sudo apt update
sudo apt install libapache2-mod-security2
On CentOS/RHEL:
sudo yum install mod_security
Step 2: Enable the Module
sudo a2enmod security2
sudo systemctl restart apache2
On CentOS/RHEL it’s usually enabled automatically after install; confirm with:
httpd -M | grep security
Step 3: Set Up the Base Configuration
The package typically installs a recommended config file. I copy it to activate it:
sudo cp /etc/modsecurity/modsecurity.conf-recommended /etc/modsecurity/modsecurity.conf
Step 4: Start in Detection Mode First
This is the single most important piece of advice I can give: never flip straight to blocking mode on a live site. I always start in DetectionOnly mode so I can review logs for false positives before anything actually gets blocked:
SecRuleEngine DetectionOnly
I leave this running for at least a few days to a week on a real production site, reviewing the audit log for anything that would have blocked legitimate traffic.
Step 5: Install the OWASP Core Rule Set
The base mod_security install has minimal rules on its own — the real protection comes from the OWASP CRS.
cd /etc/modsecurity
sudo git clone https://github.com/coreruleset/coreruleset.git
sudo mv coreruleset/crs-setup.conf.example coreruleset/crs-setup.conf
Then I include the ruleset in the main Apache config, typically in /etc/apache2/mods-enabled/security2.conf:
<IfModule security2_module>
SecDataDir /var/cache/modsecurity
IncludeOptional /etc/modsecurity/*.conf
IncludeOptional /etc/modsecurity/coreruleset/crs-setup.conf
IncludeOptional /etc/modsecurity/coreruleset/rules/*.conf
</IfModule>
Step 6: Restart and Verify
sudo apachectl configtest
sudo systemctl restart apache2
I then check that the module is actively processing requests:
sudo tail -f /var/log/apache2/modsec_audit.log
Trigger a harmless test request with an obvious attack pattern to confirm logging is working:
curl "http://yourdomain.com/?test=<script>alert(1)</script>"
You should see an entry appear in the audit log, even in detection mode.
Step 7: Review Logs and Tune False Positives
This is the step most people skip, and it’s the reason mod_security gets a bad reputation for “breaking sites.” I go through the audit log methodically and look for legitimate requests that triggered rules. For each false positive, I add a targeted exception rather than disabling the rule globally:
SecRuleRemoveById 942100
Or scope the exception to a specific URL:
<LocationMatch "/wp-admin/admin-ajax.php">
SecRuleRemoveById 942100
</LocationMatch>
Step 8: Switch to Blocking Mode
Once I’m confident false positives are handled, I flip the switch:
SecRuleEngine On
Restart Apache and monitor closely for the first 24-48 hours after enabling blocking mode.
sudo systemctl restart apache2
Step 9: Set the Paranoia Level
The OWASP CRS has a “paranoia level” that controls how aggressive the rules are. I almost always start at level 1 (the default, least aggressive) and only increase it if the specific application demands stricter security, since higher levels significantly increase false positive risk:
SecAction \
"id:900000,\
phase:1,\
nolog,\
pass,\
t:none,\
setvar:tx.paranoia_level=1"
Real-World Use Case
For a client running a custom PHP application with a known SQL injection vulnerability that couldn’t be patched immediately due to a code freeze, I used mod_security as a virtual patch — writing a targeted rule to block the specific injection pattern at the WAF level while the development team worked on a proper fix. It bought them two weeks of safety without touching a line of application code.
Common Mistakes to Avoid
- Enabling blocking mode immediately without a detection-mode testing period, which frequently causes false positives that break legitimate site functionality (like file uploads or search forms).
- Disabling entire rule categories instead of scoping exceptions narrowly, which reopens the exact attack surface you were trying to close.
- Never updating the Core Rule Set, missing coverage for newly discovered attack patterns.
- Ignoring the audit log entirely after setup, missing both false positives and genuine attack attempts.
Troubleshooting Tips
If legitimate form submissions or file uploads start failing after enabling blocking mode, check the modsec_audit.log for the specific rule ID that triggered and add a scoped exception for that endpoint rather than disabling mod_security altogether. If performance seems impacted on a high-traffic site, consider tuning SecRequestBodyLimit and disabling response body inspection for endpoints that don’t need it.
Security and Performance Best Practices
- Keep the OWASP Core Rule Set updated regularly —
cd /etc/modsecurity/coreruleset && sudo git pull. - Combine mod_security with rate limiting and IP blocking for layered defense.
- Monitor the audit log with a log analysis tool or SIEM for ongoing visibility into attack attempts.
- Always test rule changes in staging before deploying to production.
Frequently Asked Questions
Will mod_security slow down my website? There’s a small performance overhead from request inspection, but on modern hardware it’s generally negligible compared to the security benefit, especially if you tune the paranoia level appropriately.
Do I need the OWASP Core Rule Set, or can I write my own rules? The CRS gives you broad, community-vetted coverage out of the box. Custom rules are useful for application-specific virtual patching, but I always start with the CRS as the foundation.
What’s the difference between DetectionOnly and On? DetectionOnly logs matched rules without blocking anything, letting you review for false positives. On mode actually blocks requests matching a rule.
Can mod_security replace regular security patching? No. It’s a defense-in-depth layer, not a substitute for keeping your application code and dependencies updated.
Summary and Key Takeaways
mod_security combined with the OWASP Core Rule Set gives your Apache server real, application-layer attack protection against SQL injection, XSS, and other common exploits. The key to a smooth rollout is patience: start in detection mode, review logs thoroughly, scope your exceptions narrowly, and only move to blocking mode once you’re confident false positives are handled.