Skip to content
← All articles

HTTP 429 Too Many Requests: Rate Limiting Explained

The 429 Too Many Requests status is the server's signal: "you are too fast, slow down." It is the standard response from a rate limiter protecting API документацию and websites from abuse. Let us cover how to implement 429 correctly on the server, how a client should handle Retry-After, and what to do when your service receives 429 from an external API.

What 429 Means

Per RFC 6585 §4, 429 indicates the user has sent too many requests in a given time. Unlike 503 (service unavailable) or 403 (forbidden by policy), 429 is a temporary state — the client may retry after Retry-After.

Mandatory header: Retry-After: 60 (seconds) or a date. Recommended informal headers:

Rate Limiting Strategies

Token Bucket

A bucket of tokens refills at a fixed rate. Each request consumes a token. Empty = 429.

Leaky Bucket

A fixed-size queue drained at a constant rate. Overflow = 429.

Fixed Window

Counter per minute/hour. Simple, but allows bursts at window boundaries.

Sliding WindowHybrid: accounts for the previous window proportionally. The smoothest approach.

nginx Configuration

# Zone: 10MB fits ~160k unique IPs
limit_req_zone $binary_remote_addr zone=api:10m rate=60r/m;
limit_req_zone $binary_remote_addr zone=login:10m rate=5r/m;

server {
    location /api/ {
        limit_req zone=api burst=20 nodelay;
        limit_req_status 429;
        proxy_pass http://backend;
    }

    location /api/auth/login {
        limit_req zone=login burst=3 nodelay;
        limit_req_status 429;
        proxy_pass http://backend;
    }
}

Parameters:

Application-Level Rate Limiting (Redis sliding window)

<?php
function checkRateLimit(Redis $redis, string $key, int $max, int $window): array {
    $now = microtime(true);
    $pipe = $redis->multi();
    $pipe->zRemRangeByScore($key, 0, $now - $window);
    $pipe->zAdd($key, $now, "$now:" . bin2hex(random_bytes(4)));
    $pipe->zCard($key);
    $pipe->expire($key, $window * 2);
    $results = $pipe->exec();

    $count = $results[2];
    return [
        'allowed' => $count <= $max,
        'limit' => $max,
        'remaining' => max(0, $max - $count),
        'reset' => time() + $window,
    ];
}

// Usage
$rl = checkRateLimit($redis, "rl:user:{$userId}", 60, 60);
header("X-RateLimit-Limit: {$rl['limit']}");
header("X-RateLimit-Remaining: {$rl['remaining']}");
header("X-RateLimit-Reset: {$rl['reset']}");

if (!$rl['allowed']) {
    http_response_code(429);
    header('Retry-After: 60');
    header('Content-Type: application/json');
    echo json_encode(['error' => 'Too many requests']);
    exit;
}

Client-side 429 Handling (exponential backoff)

async function fetchWithRetry(url, options = {}, maxRetries = 5) {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    const res = await fetch(url, options);

    if (res.status !== 429) return res;

    const retryAfter = res.headers.get('Retry-After');
    const waitMs = retryAfter
      ? parseInt(retryAfter, 10) * 1000
      : Math.min(1000 * Math.pow(2, attempt), 60000);

    console.warn(`429 received, retrying in ${waitMs}ms`);
    await new Promise(r => setTimeout(r, waitMs));
  }
  throw new Error('Rate limit exceeded after retries');
}

Never retry aggressively without backoff — it worsens the rate limit and may trigger a WAF ban.

Client Identification: IP, User, API Key

The right tracker depends on context:

// Hierarchy: user ID > API key > IP
function getRateLimitKey(Request $req): string {
    if ($req->user?->id) return "user:{$req->user->id}";
    if ($req->apiKey) return "apikey:{$req->apiKey}";
    return "ip:{$req->ip}";
}

Getting 429 from External API: What to Do

  1. Read Retry-After — it is not a suggestion, it is a requirement.
  2. Use a circuit breaker — temporarily stop hitting the degrading API.
  3. Cache responses in Redis with a sensible TTL.
  4. Batch requests when the API supports it.
  5. Request a limit increase — paid tiers usually offer more.

To monitor your API availability, use Enterno.io Monitors with expected_code=200 — detect 429 regressions instantly.

429 vs 503 vs 403

CodeWhen to Use
429Client exceeded rate limit
503Server overloaded (general, not client-specific)
403Access policy (client fully blocked)

Frequently Asked Questions

Q: Does 429 affect SEO?
A: For Googlebot — yes, if frequent. Googlebot reduces crawl rate. Honor Retry-After and never block official Googlebot IPs.

Q: What is an adequate API rate limit?
A: It depends. Typical values: 60 req/min for general APIs, 5 req/min for login, 10 req/sec for realtime endpoints.

Q: Can 429 be bypassed?
A: Legit path: request a higher limit, optimize requests, cache. Bypassing via IP rotation = ToS violation and ban risk.

Conclusion

429 protects your service and partners. Configure a proper rate limit (sliding window + Redis), always return Retry-After and X-RateLimit-* headers, and use exponential backoff on the client. To monitor your API endpoints, use Enterno.io.

Check your website right now

Check now →
More articles: HTTP
HTTP
Analyzing Server Response Headers: What They Reveal About a Website
11.03.2026 · 41 views
HTTP
HTTP Redirect Chains and Their Impact on SEO
15.04.2026 · 7 views
HTTP
HTTP 301 vs 302 Redirect: Differences and When to Use Each
15.04.2026 · 5 views
HTTP
HTTP/2 vs HTTP/3: Differences and Performance Comparison
13.03.2026 · 39 views