- When an API Says "Too Many Requests"
- Why does a 429 error happen?
- Checking the response before changing anything
- What should a user do after receiving a 429?
- How should a server administrator handle 429 responses?
- Choosing a rate limit without guessing
- Does a 429 error mean a DDoS attack?
- Common client-side mistakes
- Frequently asked questions
- Sources
When an API Says “Too Many Requests”
A 429 response usually means the server is protecting itself from a request rate it considers too high. The HTTP status code says the server understood the request but is temporarily refusing to process it. The caller might be a browser, API client, cron job, worker, or several people sharing one public IP address.
I once found the same pattern in a small monitoring setup at home: a health-check script had been started twice after a service restart, so every endpoint was being queried by two workers. Nothing was broken on the server. The client was simply asking twice as often as I expected. That was a useful reminder: before changing a rate limit, find the process creating the requests.
When you see 429 Too Many Requests, do not keep refreshing the page. That only adds more requests. Check the response headers, application logs, and the process generating the traffic first.
Why does a 429 error happen?
HTTP 429 indicates that a client has sent too many requests during a defined period. RFC 6585 defines this status code and says a server may include Retry-After to tell the client when to try again. The value can be a number of seconds or an HTTP date. Servers are not required to send the header.
The exact limit depends on the provider and the application. Common approaches include:
- Counting requests from one IP address
- Counting requests per user account, API key, or session
- Applying a per-second, per-minute, or per-hour quota to an endpoint
- Limiting concurrent connections or running operations
- Applying a shared quota to users behind hosting NAT or shared hosting
- Blocking traffic through WAF, CDN, or reverse-proxy rules
This is why the same page may load from one connection and return 429 from another. A company network, mobile carrier, or public Wi-Fi may put many users behind one public IP address. If the server cannot distinguish them, their traffic can look like one client.
How is 429 different from 403 and 503?
These codes are often mixed up because they can appear on the same generic error page. A 429 points to a request-rate or quota problem. A 403 means the server understood the request but refuses access. A 503 means the service is temporarily unavailable; overload can cause it, but not every 503 is a rate-limit response.
Here is the distinction I use when checking an incident:
| Status code | Typical meaning | First check |
|---|---|---|
| 429 | Too many requests or quota exceeded | Rate limit, Retry-After, request frequency |
| 403 | Access denied or blocked by a security rule | Authentication, IP, and WAF rules |
| 503 | Service temporarily unavailable | Application, upstream, and resource usage |
| 502 | Proxy received an invalid response from upstream | Backend status and reverse-proxy logs |
If you are seeing 502 rather than 429, the problem may be elsewhere. The upstream checks in How to Fix a 502 Bad Gateway Error are more appropriate in that case. Confirm the actual response status and headers instead of relying only on the title of an error page.
Checking the response before changing anything
A browser hides most of the useful diagnostic information. On the server or API side, I usually start with curl and inspect the response headers. The -I option sends a HEAD request, but some applications do not handle HEAD like GET. When that matters, I use -sS -D - -o /dev/null instead.
curl -sS -D - -o /dev/null https://example.com/api/items
The -D - option writes response headers to standard output, while -o /dev/null discards the response body. That keeps the output readable.
You might see fields like these:
HTTP/2 429
retry-after: 30
content-type: application/json
x-ratelimit-limit: 60
x-ratelimit-remaining: 0
x-ratelimit-reset: 1710000030
retry-after: 30 tells the client to wait about 30 seconds. The x-ratelimit-* fields are not standard HTTP headers; the application or provider adds them. Their absence does not prove that no rate limit exists. RFC 9110 describes the general semantics of Retry-After, while the provider’s documentation defines any custom rate-limit fields.
Measure how often the client is actually sending requests. Do not run an uncontrolled loop against a production API. Five requests with a clear delay are enough for a first look:
for i in 1 2 3 4 5; do
date -Is
curl -sS -o /dev/null -w 'HTTP %{http_code}n' https://example.com/api/items
sleep 2
done
This is for diagnosis, not for trying to defeat a limit. The timestamps and status codes show whether the problem starts at a particular request frequency. I once forgot the delay while testing a local endpoint and blamed the rate limiter for reacting quickly to my own loop (yes, I had written the test). A small, explicit sleep made the behavior obvious.
Which fields should you inspect in server logs?
The web server, proxy, and application may record different details. In an Nginx access log, inspect the status code, request path, client IP, and User-Agent together. Your log format may differ, so treat this as an example for a typical access log.
grep ' 429 ' /var/log/nginx/access.log | tail -n 20
To count repeated requests from each IP:
awk '$9 == 429 {print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -nr | head
In the standard combined log format, $9 is the HTTP status field. If your log_format is different, the column number changes. Open one log line and count the fields before copying the command blindly. Restarting a service without reading the logs changes neither the rate-limit rule nor the client behavior.
What should a user do after receiving a 429?
The steps are straightforward, but the order matters. First determine whether the response is temporary or persistent.
- Wait: If the response includes
Retry-After, do not retry before that period has passed. - Reduce request volume: Stop refreshing the page and reduce parallel API calls.
- Check the client: Browser extensions, mobile apps, scripts, and background sync jobs can all generate requests.
- Verify credentials: Some systems count failed authentication attempts toward a rate limit.
- Save the response details: Include the URL, time, status code, and request ID in a support ticket.
- Do not treat a VPN as an instant fix: The new IP may also be limited, and it may trigger another security rule.
Retry behavior matters when an application consumes an API. Instead of retrying at fixed intervals, use exponential backoff. Increase the delay after later failures, then add a little random jitter so many workers do not retry at exactly the same moment.
import random
import time
import requests
url = "https://example.com/api/items"
delay = 1
for attempt in range(5):
response = requests.get(url, timeout=10)
if response.status_code != 429:
response.raise_for_status()
data = response.json()
break
retry_after = response.headers.get("Retry-After")
wait = int(retry_after) if retry_after and retry_after.isdigit() else delay
time.sleep(wait + random.uniform(0, 0.5))
delay = min(delay * 2, 60)
This example handles a numeric Retry-After value and increases the delay when the header is missing. A production client should also parse HTTP-date values, impose a safe maximum wait, and decide whether the operation is idempotent. Automatically repeating a payment request after an unclear response can create a duplicate charge.
How should a server administrator handle 429 responses?
The goal is not to remove every limit. It is to protect legitimate traffic while controlling clients that are too aggressive. First identify which layer generated the response: CDN, WAF, Nginx, application framework, API gateway, or the upstream service itself.
Limiting request rates with Nginx
Nginx uses limit_req_zone to define a shared memory zone and limit_req to apply a request rate. A basic API example looks like this:
http {
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
server {
location /api/ {
limit_req zone=api_limit burst=20 nodelay;
proxy_pass http://backend;
}
}
}
$binary_remote_addr stores the IP address as a more memory-efficient key. rate=10r/s sets the average rate, while burst=20 allows a short burst of excess requests. With nodelay, requests inside the burst are processed immediately instead of being delayed. Once the burst is exhausted, Nginx may return 429. Do not copy these values across an entire site without measuring first.
Login, search, and static-file endpoints do not have the same cost. A login endpoint may need a low, separate limit. If static files share that same rate-limit zone, one page load can consume the counter unnecessarily.
Test the configuration after making a change:
sudo nginx -t
sudo systemctl reload nginx
reload normally loads the new configuration without cutting existing connections. If the test fails, the reload does not proceed. Running nginx -t first is a small habit that prevents a surprisingly large class of mistakes. My production prompts have been red for years, partly because I learned early not to trust my memory about which machine I am on.
Application-side limits and queues
Leaving rate limiting entirely to the web server is not always enough. The application can maintain counters per API key, user, and endpoint with a shared store such as Redis. When several application servers are involved, keeping a separate in-memory counter on each machine produces inconsistent behavior: a user may hit the limit on one server but not another.
Long-running work inside an HTTP request also makes 429 and timeout problems more likely. File processing, report generation, and bulk imports are often better placed in a queue, with the client receiving a job ID. If the client polls that job, the polling endpoint also needs a sensible interval. Otherwise the status endpoint becomes the new bottleneck.
Why can IP-based limits cause trouble?
An IP address is a convenient key, but it is not a user identity. IPv4 NAT, company networks, mobile carriers, and IPv6 transitions can make different users appear as one client, or make one account appear under several addresses. If you have reliable authentication, consider user- or API-key-based limits alongside IP-based protection.
Do not blindly trust X-Forwarded-For when finding the original client IP behind a proxy. Use it only when it is set by a proxy you trust. Otherwise, a client can submit a fabricated address and evade the limit. For background on the proxy layer, see What Is a Proxy Server and How Does It Work?
Choosing a rate limit without guessing
There is no universal correct number. Set limits by measuring endpoint cost, expected traffic, client behavior, and backend capacity. A request rate that is fine for a home page may be excessive for an expensive search query or a login endpoint.
I start by collecting:
- Requests per second and per minute for each endpoint
- Response times and error rates
- CPU, memory, disk I/O, and database connection counts
- Request distribution by user, API key, and IP
- Normal peak traffic compared with sudden bursts
Tell clients how the limit works. Document the quota unit, window length, 429 behavior, and use of Retry-After. If the response body is JSON, provide a machine-readable error:
{
"error": "rate_limited",
"message": "Too many requests",
"retry_after": 30
}
The client can read this field and wait in a controlled way. Document whether the authoritative retry value is in a header or the response body; different services using similar names with different meanings make integrations needlessly difficult.
Does a 429 error mean a DDoS attack?
No. A 429 can come from an ordinary client that is running too quickly, so it is not proof of an attack. A DDoS event may involve many sources, multiple URLs, or connection exhaustion. A 429 usually tells you that one layer’s request policy has been triggered.
Still, 429 records are useful during traffic analysis. Compare CPU, connection counts, upstream wait time, and network traffic over the same period. If many unrelated IPs request one endpoint at an unusual rate, evaluate CDN or upstream filtering, bot verification, and endpoint design together. Restarting the server may reset counters, but it does not remove the source.
Rate limiting is not a complete DDoS defense. Large attacks need filtering before traffic reaches the server. For smaller application-layer abuse, endpoint-specific limits, authentication, and queueing can be effective.
Common client-side mistakes
- Retrying immediately after every error: Ignoring
Retry-Afterincreases the load. - Leaving parallel requests unlimited: An uncontrolled Promise pool or worker pool can exhaust a quota quickly.
- Skipping the cache: Requesting unchanged data on every page load is wasteful.
- Fetching everything instead of paginating: Large catalogs should use pagination or delta synchronization.
- Failing to log unsuccessful requests: Without timestamps and response codes, finding the source is difficult.
- Assuming the problem is solved after removing the limit: If backend capacity has not changed, 5xx errors may replace the 429s.
Check for invisible polling loops, especially in JavaScript. Timers can continue running when a tab is in the background, WebSocket reconnect logic can accidentally start twice, and unlimited retries after failed requests are all common causes.
Frequently asked questions
How long does Too Many Requests last?
There is no fixed duration. If the server sends Retry-After, follow it. Otherwise, check the provider’s quota window and documentation.
Can refreshing the page fix a 429?
Usually not. Each refresh sends another request and may extend the limit window. Wait, stop background automation, and check the provider’s quota status before changing networks.
What should I do when an API returns 429?
Read Retry-After and the provider’s rate-limit headers. Use exponential backoff, jitter, controlled concurrency, and endpoint-appropriate caching. Do not automatically retry non-idempotent operations.
How do I disable Nginx 429 responses?
First identify which limit_req rule generated the response and why it triggered. Adjust the endpoint, client key, and burst value instead of removing protection entirely, then validate the configuration with nginx -t.
When I see a 429 now, I ask who is generating the requests and how quickly before I touch the server configuration. Quite often, the fix is one duplicate worker or one forgotten retry loop away.
Sources
- RFC 6585 – Additional HTTP Status Codes — rfc-editor.org
- RFC 9110 – Retry-After Field — rfc-editor.org
- NGINX – Request Rate Limiting — nginx.org
- MDN – 429 Too Many Requests — developer.mozilla.org
Türkçe
English
فارسی
Русский