Nginx in front of your application can absorb two common problems: traffic floods and malicious requests. This walkthrough adds request rate limiting with Nginx’s built-in limit_req, then puts a web application firewall in front of your app using ModSecurity v3 and the OWASP Core Rule Set. By the end you will watch Nginx return 429 to a flood and 403 to an SQL injection attempt.
Prerequisites
- Ubuntu 22.04 or 24.04 with Nginx installed and serving a site.
- A test client (curl is enough).
Step 1: Add rate limiting
Rate limiting in Nginx uses a shared memory zone keyed on client IP. In the http block of /etc/nginx/nginx.conf, define the zones:
limit_req_zone $binary_remote_addr zone=perip:10m rate=10r/s;
limit_conn_zone $binary_remote_addr zone=connperip:10m;Then apply them where they matter. Protect a login endpoint harder than the rest of the site:
location /login {
limit_req zone=perip burst=20 nodelay;
limit_conn connperip 10;
limit_req_status 429;
proxy_pass http://your_app;
}rate=10r/s allows ten requests per second per IP, burst=20 tolerates short spikes, and nodelay serves the burst immediately rather than queuing it. Test and reload:
sudo nginx -t
sudo systemctl reload nginxFire a flood and watch it get throttled:
for i in $(seq 1 50); do curl -s -o /dev/null -w "%{http_code}\n" http://localhost/login; doneYou will see a run of 200s turn into 429s as the limit trips.
Step 2: Install the ModSecurity WAF module
Ubuntu packages the ModSecurity v3 connector for Nginx:
sudo apt update
sudo apt install -y libnginx-mod-http-modsecurityFetch the recommended base config and the character mapping file:
sudo mkdir -p /etc/nginx/modsec
sudo curl -s https://raw.githubusercontent.com/owasp-modsecurity/ModSecurity/v3/master/modsecurity.conf-recommended \
-o /etc/nginx/modsec/modsecurity.conf
sudo curl -s https://raw.githubusercontent.com/owasp-modsecurity/ModSecurity/v3/master/unicode.mapping \
-o /etc/nginx/modsec/unicode.mappingThe recommended config ships in detection-only mode. Turn actual blocking on:
sudo sed -i 's/^SecRuleEngine DetectionOnly/SecRuleEngine On/' /etc/nginx/modsec/modsecurity.confStep 3: add the OWASP Core Rule Set
The Core Rule Set (CRS) is the community-maintained rule pack that catches SQL injection, cross-site scripting, and the rest of the common attack classes:
sudo git clone https://github.com/coreruleset/coreruleset /etc/nginx/modsec/crs
sudo cp /etc/nginx/modsec/crs/crs-setup.conf.example /etc/nginx/modsec/crs/crs-setup.confWire it all together in a main include file:
sudo tee /etc/nginx/modsec/main.conf > /dev/null <<'EOF'
Include /etc/nginx/modsec/modsecurity.conf
Include /etc/nginx/modsec/crs/crs-setup.conf
Include /etc/nginx/modsec/crs/rules/*.conf
EOFThen enable ModSecurity in your server block:
server {
modsecurity on;
modsecurity_rules_file /etc/nginx/modsec/main.conf;
location / {
proxy_pass http://your_app;
}
}Test the config and reload:
sudo nginx -t
sudo systemctl reload nginxStep 4: confirm it blocks an attack
Send a classic SQL injection probe:
curl -s -o /dev/null -w "%{http_code}\n" "http://localhost/?id=1'%20OR%20'1'='1"You should get a 403. Confirm the WAF logged why:
sudo tail /var/log/nginx/error.logYou will see a ModSecurity entry naming the matched CRS rule.
What goes wrong
Attacks are logged but not blocked. SecRuleEngine is still DetectionOnly. Set it to On.
Legitimate traffic gets 403s. CRS false positives, common with file uploads and rich text. Do not disable the WAF; raise the anomaly threshold in crs-setup.conf or add targeted rule exclusions for the specific paths.
Real users hit 429s. Your rate or burst is too tight for real usage patterns. Measure normal traffic first, then set the limit above the real peak.
Module fails to load after an Nginx upgrade. The connector is built against a specific Nginx version. Reinstall libnginx-mod-http-modsecurity after upgrading Nginx.