Rate limiting and connection throttling are essential defences against brute-force attacks, credential stuffing, and high-volume web scrapers. Nginx provides two built-in modules — ngx_http_limit_req_module for request-rate limiting and ngx_http_limit_conn_module for connection limiting — both compiled into the default binary. On RHEL 8 these capabilities are available immediately after installing Nginx from the AppStream module stream with no additional packages required. This tutorial demonstrates how to configure both modules, return RFC-compliant 429 responses, test the limits with ab, and exempt trusted IP ranges using a geo block.
Prerequisites
- RHEL 8 server with Nginx installed and running
- Root or
sudoaccess httpd-toolsinstalled (provides theabbenchmarking tool)- Basic familiarity with Nginx configuration structure (
http,server, andlocationblocks) - A test virtual host or the default Nginx site to apply limits against
Step 1 — Install httpd-tools for Testing
The Apache benchmarking tool ab ships with httpd-tools, a utility package that does not require installing Apache itself.
sudo dnf install -y httpd-tools
ab -V
Step 2 — Define Rate Limiting Zones in the http Block
Open the main Nginx configuration file and add shared-memory zones for rate limiting inside the http block. Each zone stores the client IP address as the key and tracks request counts.
sudo nano /etc/nginx/nginx.conf
Add the following directives inside the http { } block, before any server blocks:
# Rate limiting: allow 10 requests per second per IP, 10 MB shared zone
limit_req_zone $binary_remote_addr zone=req_limit:10m rate=10r/s;
# Stricter zone for login endpoints: 5 requests per minute
limit_req_zone $binary_remote_addr zone=login_limit:5m rate=5r/m;
# Connection limiting: track open connections per IP, 10 MB zone
limit_conn_zone $binary_remote_addr zone=conn_limit:10m;
# Return 429 Too Many Requests instead of the default 503
limit_req_status 429;
limit_conn_status 429;
Step 3 — Apply Limits in Server and Location Blocks
Apply the zones to specific locations. The burst parameter allows a short spike above the configured rate, and nodelay processes burst requests immediately rather than queuing them.
server {
listen 80;
server_name example.com;
# Global request rate: burst of 20 with nodelay
limit_req zone=req_limit burst=20 nodelay;
# Limit concurrent connections per IP to 10
limit_conn conn_limit 10;
location / {
root /usr/share/nginx/html;
index index.html;
}
# Strict rate limit on the login endpoint
location /login {
limit_req zone=login_limit burst=5 nodelay;
limit_conn conn_limit 3;
proxy_pass http://127.0.0.1:8080;
}
}
Test and reload Nginx:
sudo nginx -t && sudo systemctl reload nginx
Step 4 — Test Rate Limiting with ab
Use Apache Bench to send 100 requests with 20 concurrent connections. Requests that exceed the limit will receive HTTP 429.
ab -n 100 -c 20 http://localhost/
# Look for "Non-2xx responses" in the output — those are the rate-limited requests
# Confirm in the Nginx error log
sudo tail -20 /var/log/nginx/error.log | grep limiting
The error log will show messages like limiting requests, excess: X.XXX by zone "req_limit" for throttled requests.
Step 5 — Whitelist Trusted IPs with a geo Block
Internal load balancers, monitoring agents, and known trusted IPs should bypass rate limits. Use the geo module to set a variable that conditionally disables limiting.
# Add to the http block in nginx.conf
geo $limit_key {
default $binary_remote_addr; # apply rate limit to all by default
127.0.0.1 ""; # exempt localhost
10.0.0.0/8 ""; # exempt private 10.x.x.x range
192.168.0.0/16 ""; # exempt private 192.168.x.x range
}
# Replace the previous zone key with $limit_key
limit_req_zone $limit_key zone=req_limit:10m rate=10r/s;
limit_conn_zone $limit_key zone=conn_limit:10m;
When $limit_key is an empty string for a whitelisted IP, Nginx skips the zone lookup entirely, so those clients are never rate-limited. Reload Nginx after adding the geo block:
sudo nginx -t && sudo systemctl reload nginx
Conclusion
You have configured Nginx request-rate limiting with limit_req_zone and connection throttling with limit_conn_zone on RHEL 8. Returning 429 instead of 503 provides semantically correct feedback to API clients, and using burst with nodelay smooths legitimate traffic spikes without penalising real users. The geo block ensures that internal infrastructure and trusted partners are never subject to the same restrictions as anonymous public traffic.
Next steps: How to Enable Brotli and Gzip Compression in Nginx on RHEL 8, How to Configure Nginx with ModSecurity WAF on RHEL 8, and How to Install and Configure Caddy Web Server on RHEL 8.