Introduction
A website faces hundreds of automated attacks continuously: SQL injections, XSS, bots scanning for WordPress CVEs, etc.
ModSecurity intercepts HTTP/S requests and evaluates them against a rule set before passing them to your application. Paired with the OWASP Core Rule Set (CRS), you block:
- SQLi (SQL injection)
- XSS (Cross-site scripting)
- RFI / LFI (Remote / Local File Inclusion)
- Command injection
- Scanners (Nikto, sqlmap, etc.)
- Known CVE exploits (Log4Shell, Heartbleed, etc.)
Prerequisites
- Nginx installed (1.18+)
- VPS Debian / Ubuntu
- Root access
Step 1: Installation
sudo apt update
sudo apt install -y libmodsecurity3 libmodsecurity-dev nginx-module-modsecurity
Enable module in /etc/nginx/nginx.conf:
load_module modules/ngx_http_modsecurity_module.so;
http {
modsecurity on;
modsecurity_rules_file /etc/nginx/modsec/main.conf;
}
Step 2: Base configuration
sudo mkdir -p /etc/nginx/modsec
sudo cp /etc/modsecurity/modsecurity.conf-recommended /etc/nginx/modsec/modsecurity.conf
sudo nano /etc/nginx/modsec/modsecurity.conf
Set SecRuleEngine:
SecRuleEngine DetectionOnly
⚠️ Start in DetectionOnly (log only) to identify false positives before switching to On (blocking).
Step 3: Install OWASP CRS
cd /etc/nginx/modsec
sudo git clone https://github.com/coreruleset/coreruleset.git
sudo cp coreruleset/crs-setup.conf.example coreruleset/crs-setup.conf
Step 4: main.conf
sudo nano /etc/nginx/modsec/main.conf
Include /etc/nginx/modsec/modsecurity.conf
Include /etc/nginx/modsec/coreruleset/crs-setup.conf
Include /etc/nginx/modsec/coreruleset/rules/*.conf
Step 5: Test and activate
sudo nginx -t
sudo systemctl reload nginx
SQLi test:
curl "http://your-site.com/?id=1' OR '1'='1"
Check logs:
sudo tail -f /var/log/modsec_audit.log
Step 6: Tune false positives
Let it run in DetectionOnly for a few days then analyze:
sudo grep -E "MATCHES|Detected" /var/log/modsec_audit.log | head
Exclude a rule for a specific site:
server {
location /admin/ {
modsecurity_rules '
SecRuleRemoveById 941100
SecRuleRemoveById 942100
';
}
}
Or globally, create /etc/nginx/modsec/coreruleset/rules/REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf:
SecRule REQUEST_URI "@beginsWith /api/" "id:1001,phase:1,nolog,allow,ctl:ruleRemoveById=920420"
Step 7: Enable blocking mode
Once false positives sorted:
SecRuleEngine On
sudo systemctl reload nginx
ModSecurity now blocks with HTTP 403.
Step 8: Paranoia levels
CRS supports 4 levels (1-4):
- PL1: essential rules, few false positives (default)
- PL2: stricter
- PL3: strict, many FPs likely
- PL4: paranoid, critical contexts only
SecAction "id:900000,phase:1,nolog,pass,t:none,setvar:tx.paranoia_level=2"
Step 9: Anomaly scoring
SecAction "id:900110,phase:1,nolog,pass,t:none,
setvar:tx.inbound_anomaly_score_threshold=5,
setvar:tx.outbound_anomaly_score_threshold=4"
Lower the threshold for stricter blocking.
Step 10: Custom rules
Block an IP:
SecRule REMOTE_ADDR "@ipMatch 1.2.3.4" "id:1000,phase:1,deny,status:403"
Block a User-Agent:
SecRule REQUEST_HEADERS:User-Agent "@contains sqlmap" "id:1001,phase:1,deny,status:403"
Rate-limit on /login:
SecAction "id:1002,phase:1,nolog,pass,initcol:ip=%{REMOTE_ADDR},chain"
SecRule REQUEST_URI "@beginsWith /login" "chain"
SecAction "id:1003,setvar:ip.attempts=+1,expirevar:ip.attempts=60"
SecRule IP:attempts "@gt 5" "id:1004,deny,status:429"
Step 11: Performance
ModSecurity adds 5-15ms per request on average:
SecRequestBodyAccess On
SecRequestBodyLimit 13107200
SecRequestBodyNoFilesLimit 131072
location / {
modsecurity on;
}
location /static/ {
modsecurity off;
}
Step 12: Monitoring
Daily block count:
sudo grep "Access denied" /var/log/nginx/error.log | wc -l
Top triggered rules:
sudo grep "Matched" /var/log/modsec_audit.log | grep -oP 'id "\d+"' | sort | uniq -c | sort -rn | head
Troubleshooting
"modsecurity: SecRuleEngine On - failed"
Invalid rule syntax. Check:
sudo nginx -t
Too many false positives
Go back to DetectionOnly, analyze patterns, tune with targeted exclusions.
Performance degraded
Disable body scan for large uploads:
location /upload {
modsecurity_rules '
SecRequestBodyAccess Off
';
}
Logs too verbose
SecAuditLogParts ABCFHZ
SecAuditEngine RelevantOnly
SecAuditLogRelevantStatus "^(?:5|4(?!04))"
Useful commands
sudo nginx -t
sudo systemctl reload nginx
sudo tail -f /var/log/modsec_audit.log
sudo tail -f /var/log/nginx/error.log
sudo grep "id \"941100\"" /var/log/modsec_audit.log
# Update CRS
cd /etc/nginx/modsec/coreruleset
sudo git pull
sudo systemctl reload nginx
Conclusion
ModSecurity + OWASP CRS blocks most automated web attacks without modifying your application. Pros:
- Coverage of common web CVEs
- Free OWASP updates
- Detailed logs for analysis
- Acceptable performance (<15ms)
Going further:
- Combine with Fail2ban to persistently block repeat offenders
- Use CrowdSec for collaborative intelligence
- For SaaS, also consider NAXSI (lighter alternative)
Resources
- ModSecurity: https://github.com/owasp-modsecurity/ModSecurity
- OWASP CRS: https://coreruleset.org
- CRS documentation: https://coreruleset.org/docs/
- Rules list: https://github.com/coreruleset/coreruleset/tree/v4.0/dev/rules


















