Rate limiting is a server-side defence that restricts how many requests a client can make within a time window. Without rate limiting, a single IP address or bot can flood your server with thousands of requests per second — crashing your application, exhausting your database connection pool, or triggering denial-of-service conditions. Nginx provides two rate limiting modules: ngx_http_limit_req_module (request rate limiting) and ngx_http_limit_conn_module (connection count limiting). Both are compiled into Nginx by default on RHEL 9. Rate limiting is applied to specific endpoints (login pages, APIs, uploads) rather than blanket-applied to all traffic, using shared memory zones to track per-IP state. This guide covers configuring both rate and connection limiting, handling burst traffic gracefully, whitelisting trusted IPs, and logging rejected requests for analysis.

Prerequisites

  • Nginx installed on RHEL 9

Step 1 — Define a Rate Limit Zone

Rate limit zones are defined in the http block and referenced in server or location blocks:

# Add inside the http {} block in /etc/nginx/nginx.conf
http {
    # Zone named 'api' — 10 MB shared memory, 10 requests per second per IP
    limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;

    # Stricter zone for login endpoints — 1 request per second per IP
    limit_req_zone $binary_remote_addr zone=login:10m rate=1r/s;

    # Connection count zone — 20 simultaneous connections per IP
    limit_conn_zone $binary_remote_addr zone=connlimit:10m;
}

Step 2 — Apply Rate Limiting to a Location

server {
    listen      80;
    server_name app.example.com;

    # Rate limit the login endpoint strictly
    location /login {
        limit_req   zone=login burst=5 nodelay;
        limit_req_status 429;    # Return 429 Too Many Requests (not 503)
        proxy_pass  http://localhost:3000;
    }

    # Rate limit API with burst allowance
    location /api/ {
        limit_req  zone=api burst=20 nodelay;
        limit_conn connlimit 20;
        limit_req_status 429;
        proxy_pass http://localhost:4000;
    }

    # Unrestricted for static assets
    location /static/ {
        alias /var/www/myapp/static/;
    }
}

Key parameters:

  • burst=20 — allow up to 20 queued excess requests before rejecting
  • nodelay — process burst requests immediately rather than spacing them out over time
  • limit_req_status 429 — return HTTP 429 (standard status for rate limiting) instead of 503

Step 3 — Whitelist Trusted IPs

# In the http {} block
geo $limit {
    default         1;          # Everyone else is rate limited
    10.0.0.0/8     0;          # Internal network: no limit
    203.0.113.5/32 0;          # Monitoring server: no limit
}

map $limit $limit_key {
    0 "";                       # Empty key = no rate limiting
    1 $binary_remote_addr;     # Use IP as key for rate limiting
}

limit_req_zone $limit_key zone=api:10m rate=10r/s;

Step 4 — Log Rate-Limited Requests

# Add to the http block for a custom log with the 429 status
log_format ratelimit '$remote_addr - $remote_user [$time_local] '
                     '"$request" $status $body_bytes_sent '
                     '"$http_referer" "$http_user_agent" '
                     'rt=$request_time';

# In the server block
error_log /var/log/nginx/ratelimit.log warn;
# Rate limit rejections are logged as "limiting requests" at warn level
# Monitor rate limit rejections
grep "limiting requests" /var/log/nginx/error.log | tail -20

Step 5 — Connection Limiting

server {
    listen 80;
    server_name app.example.com;

    # Limit simultaneous connections from a single IP
    limit_conn connlimit 20;
    limit_conn_status 429;

    # Limit download bandwidth per connection
    limit_rate_after 10m;   # No limit for first 10MB
    limit_rate        1m;   # Then limit to 1 MB/s per connection

    location / {
        proxy_pass http://localhost:3000;
    }
}

Step 6 — Test the Rate Limiting

# Send 20 rapid requests and observe the 429s
for i in {1..20}; do
    curl -s -o /dev/null -w "%{http_code}n" http://app.example.com/login
done

# Apache Bench load test
dnf install -y httpd-tools
ab -n 100 -c 10 http://app.example.com/api/users

Verification Checklist

nginx -t
curl -si http://app.example.com/login | head -3
grep "limiting requests" /var/log/nginx/error.log | tail -5

Conclusion

Nginx rate limiting on RHEL 9 protects login endpoints, APIs, and upload forms from brute force and denial-of-service attacks with minimal configuration. The combination of limit_req for request rate and limit_conn for simultaneous connection counts provides multi-layer protection, while the geo and map directives allow whitelisting trusted internal IPs without disabling the feature globally.

Next steps: How to Enable Brotli and Gzip Compression in Nginx on RHEL 9, How to Configure Nginx FastCGI Caching on RHEL 9, and How to Set Up ModSecurity WAF with Apache on RHEL 9.