HTTP 429 Too Many Requests: rate limiting

Cos'e HTTP 429 Too Many Requests

Il codice HTTP 429 Too Many Requests, definito dalla RFC 6585, indica che il client ha effettuato troppe richieste in un periodo di tempo e il server applica rate limiting. E uno dei codici più comuni nelle API moderne, fondamentale per proteggere infrastrutture da abuse e DoS.

Header Retry-After

L'header chiave nelle risposte 429 e Retry-After, che indica al client quanti secondi attendere prima di ritentare:

HTTP/1.1 429 Too Many Requests
Retry-After: 60
Content-Type: application/json

{
  "error": "Rate limit exceeded",
  "limit": 100,
  "window": "1 minute",
  "retry_after": 60
}

Retry-After puo essere espresso come secondi o come data HTTP-date:

Retry-After: 120
Retry-After: Mon, 29 Jun 2026 12:00:00 GMT

Header X-RateLimit-*

Molti servizi includono header informativi non-standard ma diffusi:

X-RateLimit-Limit: 1000          # richieste max per window
X-RateLimit-Remaining: 0         # rimaste in questa window
X-RateLimit-Reset: 1719654000    # epoch del reset (Unix timestamp)

GitHub, Twitter, Stripe usano variazioni simili. La RFC draft-ratelimit-headers sta standardizzando.

Algoritmi di rate limiting

I principali algoritmi:

  • Fixed window: contatore resettato ogni N secondi. Semplice ma vulnerabile a burst ai confini.
  • Sliding window: media tra finestra corrente e precedente. Più smooth.
  • Token bucket: bucket pieno con N token, refill costante; ogni richiesta consuma 1 token. Permette burst.
  • Leaky bucket: coda con dequeue costante; eccesso scartato. Smoothing rigoroso.

Token bucket esempio

class TokenBucket:
    def __init__(self, capacity, refill_rate):
        self.capacity = capacity
        self.tokens = capacity
        self.refill_rate = refill_rate  # token/secondo
        self.last_refill = time.time()

    def allow(self):
        now = time.time()
        elapsed = now - self.last_refill
        self.tokens = min(self.capacity, self.tokens + elapsed * self.refill_rate)
        self.last_refill = now
        if self.tokens >= 1:
            self.tokens -= 1
            return True
        return False

Granularita: per IP, per user, per API key

Diversi livelli di rate limit possono coesistere:

  • Per IP: protegge da abuse anonimo (es. 100 req/min per IP).
  • Per API key: tier di abbonamento (free: 1000/giorno, pro: 100000/giorno).
  • Per endpoint: endpoint costosi più restrittivi (es. /search: 10/min, /static: 1000/min).
  • Per user: anche utenti diversi sotto stessa API key.

Implementazione Nginx

http {
    limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;

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

Implementazione Express.js

const rateLimit = require('express-rate-limit');

const limiter = rateLimit({
    windowMs: 60 * 1000,
    max: 100,
    standardHeaders: true,
    legacyHeaders: false,
    message: { error: 'Too many requests' }
});

app.use('/api/', limiter);

Implementazione Laravel

// routes/api.php
Route::middleware('throttle:60,1')->group(function () {
    Route::get('/products', [ProductController::class, 'index']);
});
// 60 richieste per minuto per utente authenticated

Distributed rate limiting

In ambienti multi-server, il rate limit deve essere globale. Soluzioni:

  • Redis: contatori atomici con INCR/EXPIRE.
  • Memcached: simile ma più volatile.
  • API Gateway centralizzato: Kong, AWS API Gateway, Cloudflare gestiscono globalmente.

Best practice client

  • Honor Retry-After: aspetta prima di ritentare.
  • Exponential backoff: se senza Retry-After, raddoppia attese (1s, 2s, 4s, 8s...).
  • Jitter: aggiungi randomness per evitare thundering herd.
  • Circuit breaker: dopo N fallimenti consecutivi, pausa lunga.
  • Cache aggressivo: riduci richieste verso endpoint stabili.

Pattern di retry

async function fetchWithRetry(url, maxRetries = 5) {
    for (let i = 0; i < maxRetries; i++) {
        const res = await fetch(url);
        if (res.status !== 429) return res;

        const retryAfter = parseInt(res.headers.get('Retry-After') || '1');
        const jitter = Math.random() * 1000;
        await new Promise(r => setTimeout(r, retryAfter * 1000 + jitter));
    }
    throw new Error('Max retries exceeded');
}

429 vs 503 Service Unavailable

  • 429: il problema e specifico del client (sta facendo troppe richieste).
  • 503: il problema e del server (sovraccarico globale).

Entrambi possono usare Retry-After. Il client gestisce 429 con retry mirato; 503 con backoff più cauto.

Sicurezza vs UX

Rate limit troppo aggressivi frustrano utenti legittimi; troppo lassi consentono abuse. Bilanciamento:

  • Identifica i pattern di traffico normali con monitoring.
  • Imposta limit al doppio del 99esimo percentile naturale.
  • Esponi più chiaramente i limit ai consumatori (documentazione, header).
  • Offri upgrade path (piani a pagamento per limit maggiori).

Rate limit gerarchici

API mature implementano rate limit a multipli livelli: globale (es. 10000 req/min totali sul cluster), per IP (100/min), per user (1000/min), per endpoint costoso (search: 10/min). Quando uno qualsiasi viene saturato, 429. Header informativi indicano quale limite e stato raggiunto.

Quotas vs rate limits

Distinzione importante: rate limit (es. 100/min) protegge dal burst; quota (es. 10000/giorno) limita uso totale. API tipiche hanno entrambi. Header X-RateLimit-* per rate, X-Quota-* per quota.

Bypass per utenti premium

Strategie comuni: tier free con limit bassi, tier paid con limit alti, tier enterprise con limit custom. Implementazione: lookup user tier prima di applicare bucket, scegliere bucket appropriato. UI deve mostrare al user vicino al limite un warning ("usato 90% del quota giornaliero").

429 e DDoS

Durante attacchi DDoS legittimi, 429 e tua prima difesa. Configurazione aggressiva: 10 req/sec per IP, ban temporaneo dopo 100 violazioni. Combinato con WAF (Cloudflare) e IP reputation. Per protezione avanzata, fingerprinting JS, CAPTCHA progressive.

Monitoring metrics

Track metriche essenziali: % di 429 rispetto al totale traffico, top IP che ricevono 429, top endpoint che generano 429. Anomalie segnalano potenziali attacchi o legitimate user che ha bisogno di tier più alto. Alert su spike improvvisi di 429.

Hai bisogno di aiuto?

Se il tuo sito mostra errori HTTP, il team di G Tech Group puo aiutarti. Contattaci tramite il modulo di contatto.

Hai trovato utile quest'articolo?