ModSecurity is the Web Application Firewall that sits in front of your NGINX-served app and blocks attacks before they ever reach your PHP, your database, or your unsuspecting plugins. SQL injection. Cross-site scripting. Path traversal. File inclusion. Scanner traffic. Bad bots. They all have known HTTP fingerprints, and ModSecurity NGINX setup with the OWASP Core Rule Set blocks the vast majority of them automatically. This is the NGINX modsecurity setup guide for the Debian and Ubuntu way — packaged, signed, and ready in about ten minutes.
The myguard APT repository ships ModSecurity v3 as a pre-built dynamic NGINX module. No compiling from source, no dependency hell, no “wait, which version of libcre2 did you say?” energy. Add the repo, install one package, configure, done.
If you’d rather follow a more conversational version of this with longer explanations, our step-by-step ModSecurity tutorial walks through the same install with a friendlier tone. This page is the no-nonsense Debian/Ubuntu reference.
Why You Actually Need a WAF in 2026
Honest truth: your application has vulnerabilities you don’t know about yet. Every WordPress plugin, every Composer dependency, every random JavaScript package — they all have bugs that get discovered, exploited, and published faster than you can read the CVE feed. On any given day, real exploit attempts are running against your server right now, probing for known weaknesses.
A WAF doesn’t replace patching. But it buys you time. When a WordPress plugin CVE drops on a Monday and the patch lands on Wednesday, a properly configured NGINX web application firewall blocks the exploit attempts during those 48 hours — often because the attack pattern matches a generic OWASP CRS rule, not a CVE-specific one. That is genuinely the difference between a quiet week and a 3am incident bridge.
The OWASP Core Rule Set is maintained by a small army of security researchers, covers the OWASP Top 10 attack categories, and is the industry-standard baseline for HTTP-layer protection. ModSecurity owasp crs nginx is the combination most production sites end up with, regardless of whether they admit it.
ModSecurity v3 vs v2: What’s Different in the NGINX World
ModSecurity v2 was an Apache module. ModSecurity v3 (libmodsecurity3) is a standalone library with a connector model — the WAF logic lives in the library, and thin connector modules bridge it to your web server. This means the same engine works with NGINX, Angie, Apache, IIS, and more. One library, many doors.
What changed in v3 that matters for an NGINX WAF Debian Ubuntu deployment:
- Performance — v3 evaluates rules more efficiently, with lower per-request overhead.
- Dynamic rule loading — rules can be refreshed without restarting NGINX (with the right config).
- Better request body inspection — JSON, XML, multipart are all properly parsed.
- Custom rule compatibility — most v2 rules port, but not all directives survived the rewrite.
Step 1 — Install ModSecurity on Debian or Ubuntu
# Add the myguard repository (if you haven't yet — takes about 30 seconds)
# Full setup: /how-to-use/
# Install the ModSecurity v3 library and the NGINX connector module
sudo apt update
sudo apt install libmodsecurity3 libnginx-mod-http-modsecurity
# Verify the NGINX module loaded
nginx -V 2>&1 | grep -i modsecurity || ls /etc/nginx/modules-enabled/
Step 2 — Confirm the Module Is Loaded
NGINX needs to actually load the ModSecurity dynamic module. The myguard package drops a config snippet in /etc/nginx/modules-enabled/ automatically:
cat /etc/nginx/modules-enabled/50-mod-http-modsecurity.conf
# Should contain:
# load_module modules/ngx_http_modsecurity_module.so;
If you ever uninstall and reinstall, double-check this file exists. If it doesn’t, NGINX won’t know ModSecurity is there.
Step 3 — Configure ModSecurity Itself
# ModSecurity config directory
sudo mkdir -p /etc/nginx/modsec
# Copy the recommended base config
sudo cp /usr/share/modsecurity-crs/modsecurity.conf-recommended /etc/nginx/modsec/modsecurity.conf
# IMPORTANT: leave the engine in DetectionOnly mode for the first week
# (this is the default in the recommended config)
grep ^SecRuleEngine /etc/nginx/modsec/modsecurity.conf
# Expected: SecRuleEngine DetectionOnly
Do not skip the DetectionOnly week. Log what would be blocked, tune out false positives, then switch to SecRuleEngine On. Flipping straight to blocking on a live WordPress site is how you get an angry phone call from the marketing team.
Step 4 — Install the OWASP Core Rule Set
# Install CRS via apt (recommended on Debian and Ubuntu)
sudo apt install modsecurity-crs
# Or pull the latest directly from upstream
sudo git clone https://github.com/coreruleset/coreruleset /etc/modsecurity-crs
sudo cp /etc/modsecurity-crs/crs-setup.conf.example /etc/modsecurity-crs/crs-setup.conf
# Build the ModSecurity main include file
sudo tee /etc/nginx/modsec/main.conf > /dev/null <<'EOF'
Include /etc/nginx/modsec/modsecurity.conf
Include /etc/modsecurity-crs/crs-setup.conf
Include /etc/modsecurity-crs/rules/*.conf
EOF
Step 5 — Turn On ModSecurity in Your Server Block
server {
listen 443 ssl;
http2 on;
server_name example.com;
# Activate the NGINX WAF
modsecurity on;
modsecurity_rules_file /etc/nginx/modsec/main.conf;
# ... rest of your config (SSL, root, fastcgi_pass, etc.)
}
Reload NGINX:
sudo nginx -t && sudo systemctl reload nginx
CRS Paranoia Levels: The One Setting That Matters Most
The OWASP Core Rule Set has four paranoia levels. They control how aggressively the WAF flags requests. This is the single most important tuning decision you will make:
- Level 1 (default) — only obvious attacks. Almost no false positives. Suitable for most websites out of the box.
- Level 2 — more comprehensive. Some false positives on complex apps. The recommended target for public-facing APIs and WordPress installs.
- Level 3 — aggressive. Significant false positives. Only run this if you have time to whitelist a long tail of edge cases.
- Level 4 — paranoid. Unsuitable for most production traffic without sustained tuning effort.
Set the paranoia level in /etc/modsecurity-crs/crs-setup.conf by uncommenting the appropriate setvar:tx.paranoia_level line. Most production NGINX modsecurity setup deployments land on level 2.
WordPress-Specific CRS Tuning
WordPress generates HTTP traffic that CRS level 2 sometimes mis-flags. Large media uploads, base64-encoded Gutenberg block data, complex admin AJAX — all legitimate, all noisy. The CRS project maintains an official WordPress exclusion plugin to suppress these false positives without dropping your overall protection:
# In main.conf, the WordPress exclusions must come BEFORE the CRS rules
sudo tee /etc/nginx/modsec/main.conf > /dev/null <<'EOF'
Include /etc/nginx/modsec/modsecurity.conf
Include /etc/modsecurity-crs/crs-setup.conf
Include /etc/modsecurity-crs/plugins/wordpress-rule-exclusions-before.conf
Include /etc/modsecurity-crs/rules/*.conf
Include /etc/modsecurity-crs/plugins/wordpress-rule-exclusions-after.conf
EOF
If the exclusion files aren’t present yet, grab them from the CRS plugins repository on GitHub. They are independently versioned and shouldn’t be edited in place.
Reading the Audit Log Without Going Insane
# Tail the audit log live
tail -f /var/log/modsec_audit.log
# Or watch the NGINX error log for ModSecurity messages
tail -f /var/log/nginx/error.log | grep ModSecurity
# Aggregate top triggering rule IDs (great for triage)
grep -oE 'id "[0-9]+' /var/log/modsec_audit.log | sort | uniq -c | sort -rn | head -20
Focus on the top triggers first. If rule 920350 (Host header is an IP address) keeps firing on your uptime monitor, whitelist the monitor’s IP. If rule 942100 (SQL injection via libinjection) lights up on a legitimate API call, investigate — sometimes it really is a false positive, and sometimes you have found yourself a genuine vulnerability you didn’t know about.
Whitelisting False Positives Cleanly
When you confirm a legitimate request is being blocked, whitelist surgically. Don’t disable entire rule groups; whitelist specific rule IDs for specific URIs or IP ranges:
# /etc/nginx/modsec/exceptions.conf
# Trust the monitoring IPs entirely for rule 920350
SecRule REMOTE_ADDR "@ipMatch 192.168.1.10,10.0.0.5" \
"id:1000,phase:1,pass,nolog,ctl:ruleRemoveById=920350"
# Allow the /api/data endpoint to receive SQL-looking strings
SecRule REQUEST_URI "@beginsWith /api/data" \
"id:1001,phase:1,pass,nolog,ctl:ruleRemoveById=942100"
# Include BEFORE the CRS rules block in main.conf
Per-Location WAF Tuning
You can disable the NGINX web application firewall entirely on safe locations (such as health checks) or apply stricter rules on sensitive ones (like login pages):
server {
modsecurity on;
modsecurity_rules_file /etc/nginx/modsec/main.conf;
# No need for WAF on the health check (no user input)
location = /health {
modsecurity off;
return 200 'OK';
}
# Stricter rules for the WordPress login endpoint
location /wp-login.php {
modsecurity on;
modsecurity_rules_file /etc/nginx/modsec/strict.conf;
}
}
Keeping CRS Updated
The OWASP CRS team ships meaningful updates every few months. If you installed via apt, your package manager does the work:
sudo apt update && sudo apt upgrade modsecurity-crs
sudo nginx -t && sudo systemctl reload nginx
If you installed via git, pull the upstream repository:
cd /etc/modsecurity-crs
sudo git pull
sudo nginx -t && sudo systemctl reload nginx
Performance Impact of ModSecurity on NGINX
ModSecurity v3 with CRS at paranoia level 1-2 adds roughly 1-3ms per request on a modern Debian or Ubuntu server. On a server handling 1,000 requests per second, that translates to about 3% extra CPU at level 2 — almost always invisible to users, almost never a hosting bottleneck.
At paranoia level 3-4 the overhead climbs significantly because more rules run per request. For most sites running an NGINX WAF Debian Ubuntu stack, level 2 is the practical ceiling without dedicated WAF hardware. Easy ways to shave overhead:
- Disable ModSecurity on static-asset locations (images, CSS, JS — no attack surface).
- Use
SecRequestBodyAccess Offin locations that never receive a request body. - Skip the WAF entirely for trusted internal IPs (monitoring, load balancer health checks).
ModSecurity Plus PHP-Snuffleupagus = Defence in Depth
ModSecurity lives in NGINX and filters at the HTTP layer — that’s everything that arrives over the wire. PHP-Snuffleupagus lives inside PHP-FPM and controls what PHP is allowed to do once a request has been allowed through. Run both. ModSecurity stops the obvious. Snuffleupagus catches what slips past. Together they cover the two layers an attacker has to defeat to do real damage.
Frequently Asked Questions
Related Posts
- How to Install ModSecurity and OWASP CRS on NGINX (Step-by-Step) — the longer, beginner-friendly walkthrough.
- PHP-Snuffleupagus: Harden PHP-FPM at the Interpreter Level — the PHP-layer companion to the NGINX WAF.
- NGINX Modules — optimized and extended — ModSecurity is one of 50+ modules in the myguard NGINX build.
- TLS Configuration for NGINX and Angie — pair the WAF with solid TLS for a properly hardened front door.
- NGINX and Angie Expert Guide — the wider performance and security stack this fits inside.
- How to Add the myguard APT Repository — where these ModSecurity packages live.