How to configure rate limiting and a WAF with Nginx to block an attack


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 nginx

Fire 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; done

You 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-modsecurity

Fetch 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.mapping

The recommended config ships in detection-only mode. Turn actual blocking on:

sudo sed -i 's/^SecRuleEngine DetectionOnly/SecRuleEngine On/' /etc/nginx/modsec/modsecurity.conf

Step 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.conf

Wire 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
EOF

Then 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 nginx

Step 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.log

You 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.

BlueGrid.io Content Team

Three people pose together against a plain white background. The woman on the left is smiling with her hand on her hip, while the two men beside her stand closely, one in a hoodie and the other in a plaid shirt.

BlueGrid.io Content Team

BlueGrid.io Team is an editorial collective of engineers, practitioners, and contributors sharing insights across technology, operations, company culture, and the people behind the systems. Content is created through interviews, hands-on experience, internal collaboration, and editorial review, reflecting both how systems are built and how teams work together in real-world environments.

Share this post

Share this link via

Or copy link