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.