Troubleshooting Nginx 503 Errors: Rate Limit Exceeded on CentOS Stream / Rocky Linux
Resolve Nginx 503 Service Unavailable errors caused by rate limiting on CentOS Stream and Rocky Linux. Diagnose, adjust Nginx configs, and secure your web server.
Resolve Nginx 503 Service Unavailable errors caused by rate limiting on CentOS Stream and Rocky Linux. Diagnose, adjust Nginx configs, and secure your web server.
When your Nginx web server starts returning 503 Service Unavailable errors, especially under periods of high traffic or suspicious activity, it often indicates that a configured rate limit has been triggered. This guide delves into diagnosing and resolving "Nginx rate limit exceeded" issues on CentOS Stream and Rocky Linux distributions, ensuring your services remain available while protecting against abuse.
Symptom & Error Signature
Users attempting to access your Nginx-powered website or API will consistently receive a 503 Service Unavailable response. From a server perspective, you'll observe specific entries in your Nginx error logs, indicating that a rate limit has been reached.
Typical User Experience (Browser/curl):
curl -I https://yourdomain.com/
HTTP/1.1 503 Service Unavailable
Server: nginx
Date: Wed, 08 Nov 2023 10:30:00 GMT
Content-Type: text/html
Content-Length: 196
Connection: keep-alive
Retry-After: 5
Nginx Error Log (/var/log/nginx/error.log):
2023/11/08 10:30:00 [error] 12345#0: *123456 limiting requests, excess: 1.000 by zone "mylimit", client: 203.0.113.42, server: yourdomain.com, request: "GET /api/data HTTP/1.1", host: "yourdomain.com"
2023/11/08 10:30:01 [error] 12346#0: *123457 limiting requests, excess: 2.500 by zone "ip_burst_limit", client: 203.0.113.42, server: yourdomain.com, request: "POST /login HTTP/1.1", host: "yourdomain.com"
Nginx Access Log (/var/log/nginx/access.log):
203.0.113.42 - - [08/Nov/2023:10:30:00 +0000] "GET /api/data HTTP/1.1" 503 196 "-" "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"
203.0.113.42 - - [08/Nov/2023:10:30:01 +0000] "POST /login HTTP/1.1" 503 196 "-" "python-requests/2.28.1"
Root Cause Analysis
The 503 Service Unavailable with a "limiting requests" error in Nginx logs directly points to Nginx's built-in rate limiting modules being triggered. Specifically, this indicates that the client's request rate has exceeded the thresholds defined by the limit_req_zone and limit_req directives, or that the number of concurrent connections has surpassed limit_conn_zone and limit_conn directives.
Here are the primary underlying reasons:
- Legitimate Traffic Spike: A sudden, legitimate surge in user traffic (e.g., viral content, marketing campaign success) can exceed existing, perhaps conservative, rate limits.
- Misconfigured Rate Limits: The configured rate limits might be too strict for your application's expected normal traffic patterns, inadvertently blocking valid users.
- Malicious Activity: This is a common defense mechanism against:
- DDoS Attacks (Layer 7): Bots attempting to overwhelm the application layer with a flood of requests.
- Brute-force Attacks: Repeated login attempts, API calls, or directory scanning.
- Web Scraping: Bots excessively scraping content from your site.
- Backend/Upstream Issues (Indirect): While Nginx's rate limit is the direct cause of the 503, a struggling backend application or database might cause clients to retry requests more aggressively, inadvertently triggering Nginx's limits. However, Nginx's primary purpose here is to protect itself and the backend from being overloaded by too many requests, regardless of the backend's health.
Nginx's rate limiting is a powerful tool for maintaining stability, but it requires careful tuning to differentiate between legitimate high traffic and malicious floods.
Step-by-Step Resolution
Follow these steps to diagnose and resolve Nginx rate limit exceeded issues on your CentOS Stream or Rocky Linux server.
1. Identify the Source and Scope of the Problem
First, pinpoint which clients are hitting the rate limits and which specific limits are being triggered.
Examine Nginx Error Logs: The Nginx error log (
/var/log/nginx/error.log) is your primary source. Look for entries containinglimiting requests, excess:as shown in the "Symptom & Error Signature" section. Pay attention to:client:The IP address of the client triggering the limit.zone:The name of thelimit_req_zonethat was exceeded.request:The URI being requested.
grep "limiting requests" /var/log/nginx/error.log | tail -n 50Analyze Access Logs for Specific IPs: Once you identify problematic client IPs from the error logs, check the access logs (
/var/log/nginx/access.log) for the request patterns from those IPs. This helps determine if it's a legitimate user or a bot.# Replace 203.0.113.42 with the problematic IP grep "203.0.113.42" /var/log/nginx/access.log | lessTo identify top IPs hitting 503s due to rate limiting:
awk '$9 == "503" {print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -nr | head -n 10
2. Review Current Nginx Rate Limiting Configuration
Locate your Nginx configuration files to understand the existing rate limit directives.
Locate Nginx Configuration Files: The main Nginx configuration file is typically
/etc/nginx/nginx.conf. Many setups use included files in/etc/nginx/conf.d/or/etc/nginx/sites-enabled/for virtual hosts.grep -r "limit_req_zone|limit_req|limit_conn_zone|limit_conn" /etc/nginx/Understand Key Directives:
limit_req_zone zone_name client_variable:size rate=rate [sync];- Defines a shared memory zone for storing request states.
client_variable: Usually$binary_remote_addrfor unique client IPs.size: Size of the zone (e.g.,10mfor 10MB, which can store ~160,000 states).rate: Max request rate (e.g.,rate=1r/sfor 1 request per second,rate=30r/mfor 30 requests per minute).
limit_req zone=zone_name [burst=number] [nodelay | delay];- Applies the defined rate limit to a specific
location,server, orhttpblock. zone=zone_name: References the shared memory zone.burst=number: Allows requests to exceed the rate temporarily, buffering them up tonumber. Requests aboveburstare rejected.nodelay: Ifburstis used, requests are served immediately without delay as long as they are within the burst limit. Withoutnodelay, requests exceeding theratebut withinburstare delayed to match the rate.nodelayis often preferred for user experience.
- Applies the defined rate limit to a specific
limit_conn_zone client_variable zone=zone_name:size;- Defines a shared memory zone for storing concurrent connection states.
limit_conn zone=zone_name number;- Applies the connection limit.
numberis the maximum allowed concurrent connections per key.
- Applies the connection limit.
Example Nginx Configuration Snippet:
# In http block (e.g., /etc/nginx/nginx.conf) http { limit_req_zone $binary_remote_addr zone=mylimit:10m rate=5r/s; limit_req_zone $binary_remote_addr zone=login_limit:5m rate=1r/s; limit_conn_zone $binary_remote_addr zone=conn_limit:10m; server { listen 80; server_name yourdomain.com; # Apply a general request rate limit with burst and no delay location / { limit_req zone=mylimit burst=10 nodelay; limit_conn conn_limit 20; # Max 20 concurrent connections per IP # ... other configurations ... } # Stricter limit for a sensitive endpoint like login location /login { limit_req zone=login_limit burst=5 nodelay; # ... other configurations ... } } }
3. Adjusting Rate Limiting Parameters
Based on your analysis, choose the appropriate strategy. Always test Nginx configuration before reloading.
Blindly increasing rate limits can expose your server to DDoS attacks or resource exhaustion. Always analyze the traffic patterns before making significant changes. If you suspect an attack, consider blocking malicious IPs directly using a firewall first.
Option A: Increase Limits (If Traffic is Legitimate) If your analysis shows that legitimate users or expected traffic spikes are triggering the limits, you might need to increase the
rateorburstvalues.# Example: Increase general rate limit to 10 requests/second, allow burst of 20 # Before: limit_req_zone $binary_remote_addr zone=mylimit:10m rate=5r/s; # limit_req zone=mylimit burst=10 nodelay; # After (modify /etc/nginx/nginx.conf and/or server blocks): limit_req_zone $binary_remote_addr zone=mylimit:10m rate=10r/s; # New rate # Then in your location block: # limit_req zone=mylimit burst=20 nodelay; # New burst- Increase
rate: Allows more requests per unit of time. - Increase
burst: Allows more requests to queue up without being immediately rejected. This is good for "bursty" legitimate traffic. nodelay: Ensures that burst requests are processed immediately rather than being delayed. Use this if user experience is critical.
- Increase
Option B: Exempt Trusted IPs (Whitelisting) If specific internal tools, monitoring systems, or known partners are hitting the limits, you can whitelist their IP addresses. Use the
geomodule for this.# In http block (e.g., /etc/nginx/nginx.conf) http { geo $is_whitelisted { default 0; 192.168.1.0/24 1; # Internal network 203.0.113.10 1; # Specific trusted IP } map $is_whitelisted $limit_zone_name { 0 "mylimit"; 1 ""; # No limit zone for whitelisted IPs } # Apply limit_req based on the mapped zone name limit_req_zone $binary_remote_addr zone=mylimit:10m rate=5r/s; server { listen 80; server_name yourdomain.com; location / { # Only apply the limit if $limit_zone_name is not empty (i.e., not whitelisted) if ($limit_zone_name != "") { limit_req zone=$limit_zone_name burst=10 nodelay; } # ... } } }Option C: Implement or Refine Advanced Limiting
- Per-URI Limiting: Apply different limits to different paths. For example, a
/loginendpoint might need stricter limits than static asset requests. (Already shown in general example). - Combined Limits: You can combine multiple
limit_reqdirectives in alocationblock. Nginx will apply the strictest limit among them. - Limit by Header: Use
$http_x_forwarded_forif behind a load balancer, but be wary of spoofed headers.real_ip_headerandset_real_ip_fromshould be configured if you are behind a proxy.
# Example for $http_x_forwarded_for (if correctly set by a trusted proxy) # Ensure you have 'real_ip_header X-Forwarded-For;' and 'set_real_ip_from trusted_ip_range;' # configured in your http block if relying on this. limit_req_zone $http_x_forwarded_for zone=proxy_limit:10m rate=5r/s;- Per-URI Limiting: Apply different limits to different paths. For example, a
Option D: Implement Blocking/Firewalling (If Malicious Traffic) If you've identified specific IPs or patterns of malicious behavior, it's often better to block them at the firewall level (
firewalld) or with tools likefail2banrather than letting Nginx deal with every request.Using
firewalld(Recommended for CentOS/Rocky): To permanently block a suspicious IP:sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="203.0.113.42" drop' sudo firewall-cmd --reloadUsing
fail2ban:fail2bancan automatically monitor Nginx logs for rate limit errors and ban IPs usingfirewalld(oriptables).- Install Fail2ban:
sudo dnf install fail2ban -y - Create a jail configuration for Nginx rate limits:
Create
/etc/fail2ban/jail.d/nginx-rate-limit.conf:[nginx-rate-limit] enabled = true port = http,https filter = nginx-rate-limit logpath = /var/log/nginx/error.log maxretry = 5 findtime = 600 bantime = 86400 # Ban for 24 hours - Create a filter for Nginx rate limit errors:
Create
/etc/fail2ban/filter.d/nginx-rate-limit.conf:[Definition] failregex = limiting requests, excess:.* by zone ".*", client: <HOST>, ignoreregex = - Restart Fail2ban:
sudo systemctl enable fail2ban sudo systemctl restart fail2banfail2banwill now monitor your Nginx error logs and ban IPs that frequently trigger rate limit errors.
- Install Fail2ban:
When modifying Nginx configuration, always test the syntax before reloading to avoid downtime.
sudo nginx -tIf the test is successful, you'll see:
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok nginx: configuration file /etc/nginx/nginx.conf test is successful
4. Restart/Reload Nginx
After making any changes to Nginx configuration, you need to apply them.
sudo systemctl reload nginx
If you made changes to underlying modules or extremely critical paths, a full restart might be preferred, though reload is usually sufficient for configuration changes.
sudo systemctl restart nginx
5. Monitor and Iterate
After implementing changes, it's crucial to monitor your server's behavior and Nginx logs.
Check Logs: Continuously monitor
/var/log/nginx/error.logand/var/log/nginx/access.logto see if the 503 errors persist or if new rate limit errors appear.Server Metrics: Monitor server resource utilization (CPU, memory, network I/O) using tools like
top,htop,nmon, orGrafana/Prometheusdashboards. Ensure that Nginx isn't just shifting the problem to another resource bottleneck.User Feedback: Gather feedback from users or check your application's error reporting to confirm the
503 Service Unavailableerrors have ceased.
Rate limiting is an ongoing tuning process. You may need to adjust your limits multiple times to find the optimal balance between protection and user experience.
Our Production Verification Guarantee
Encountering a bug not covered here or running a non-standard kernel configuration? Our solutions are continually refined against real production incidents. Submit an environment trace for our editorial team to replicate.