API Rate Limiting ist die Technik, mit der man begrenzt, wie viele Anfragen ein Client innerhalb eines bestimmten Zeitfensters an deine API senden darf. Das Ziel ist nie nur eines: Missbrauch und Brute-Force-Angriffe blockieren, die Infrastrukturkosten vorhersehbar halten, verhindern, dass ein einzelner Client alle Ressourcen verbraucht und den anderen nichts übrig lässt, und eine faire Verteilung der Nutzung erzwingen. Eine gut konzipierte Limitierungsschicht macht deine API zugleich sicherer und stabiler.
Warum brauchst du Rate Limiting?
Ein ungeschützter Endpunkt kann in mehreren Szenarien schnell zusammenbrechen. Eine Endlosschleife in einem Client, ein bösartiger Bot oder ein Nutzer, der deine kostenlose Stufe ausnutzt, kann Hunderte Anfragen pro Sekunde abfeuern. Diese Situationen führen zu:
- Ressourcenerschöpfung: Datenbankverbindungen, CPU und Speicher werden von einem Client verbraucht.
- Kostenexplosion: Cloud-Rechnungen und Aufrufe von Drittanbieter-APIs wachsen unkontrolliert.
- Sicherheitsrisiko: Passwortraten, OTP-Brute-Force und Scraping werden einfach.
- Ungerechtigkeit: Ein aggressiver Nutzer ruiniert das Erlebnis für alle anderen.
Der Token-Bucket-Algorithmus
Token Bucket ist dank seiner Flexibilität die am häufigsten verwendete Methode. Die Idee ist einfach: Jeder Client hat einen Eimer mit fester Kapazität, und in gleichmäßigem Tempo werden Token hinzugefügt. Jede Anfrage verbraucht ein Token; ist der Eimer leer, wird die Anfrage abgelehnt. Da der Eimer eine Kapazität hat, toleriert er kurze Spitzen (Bursts), hält das durchschnittliche Tempo aber langfristig konstant.
class TokenBucket {
constructor(capacity, refillPerSecond) {
this.capacity = capacity;
this.tokens = capacity;
this.refillRate = refillPerSecond;
this.last = Date.now();
}
tryRemove(count = 1) {
const now = Date.now();
const elapsed = (now - this.last) / 1000;
this.tokens = Math.min(this.capacity, this.tokens + elapsed * this.refillRate);
this.last = now;
if (this.tokens >= count) {
this.tokens -= count;
return true;
}
return false;
}
}
Hier definiert capacity die zulässige Burst-Größe und refillRate legt das tragbare Durchschnittstempo fest. Bei einer Kapazität von 20 und einer Nachfüllrate von 5 pro Sekunde kann ein Client beispielsweise kurzzeitig bis zu 20 Anfragen senden, pendelt sich aber langfristig auf 5 Anfragen pro Sekunde ein.
Der Sliding-Window-Algorithmus
Ein naiver „Fixed Window“-Zähler (etwa 100 Anfragen pro Minute) erzeugt ein Randproblem: Treffen 100 Anfragen in der letzten Sekunde eines Fensters und 100 in der ersten Sekunde des nächsten ein, passieren 200 Anfragen in zwei Sekunden. Sliding Window löst das mit einem gleitenden Zeitbereich. Die genaueste Form ist das Sliding Window Log, das den Zeitstempel jeder Anfrage speichert und jene außerhalb des Fensters verwirft. Üblich ist die Umsetzung in Redis mit einem Sorted Set:
-- Zeitstempel mit ZADD hinzufügen, alte entfernen, dann zählen
local key = KEYS[1]
local now = tonumber(ARGV[1])
local window = tonumber(ARGV[2])
local limit = tonumber(ARGV[3])
redis.call('ZREMRANGEBYSCORE', key, 0, now - window)
local count = redis.call('ZCARD', key)
if count < limit then
redis.call('ZADD', key, now, now)
redis.call('PEXPIRE', key, window)
return 1
end
return 0
Die Log-Methode ist am präzisesten, speichert aber jeden Zeitstempel pro Client. Ist der Speicher knapp, bevorzuge den sogenannten Sliding Window Counter: Er gewichtet die Zähler des aktuellen und des vorherigen festen Fensters, um einen Näherungswert zu schätzen, und benötigt deutlich weniger Speicher bei einer kleinen Fehlertoleranz.
Zähler in einer verteilten Umgebung teilen
Betreibst du mehrere Anwendungsserver, multipliziert jeder Server mit seinem eigenen In-Memory-Zähler das Limit faktisch mit der Serveranzahl. Die Lösung ist, die Zähler in einem zentralen, schnellen Speicher zu halten — meist Redis. Die atomaren Befehle von Redis (INCR, Sorted-Set-Operationen) und Lua-Skripte, die in einem einzigen Round-Trip laufen, sorgen für konsistentes Zählen ohne Race Conditions. Viele Frameworks bieten das sofort: Laravel stellt die RateLimiter-Facade und eine throttle-Middleware bereit, auf der Express-Seite gibt es express-rate-limit mit einem Redis-Store, und NGINX hat die Direktive limit_req.
Korrekte HTTP-Antworten und Header
Beim Ablehnen einer Anfrage die richtigen Signale zu senden, gehört zu gutem API-Design. Gib den Standardstatuscode 429 Too Many Requests zurück und teile dem Client mit, wann er es erneut versuchen kann:
Retry-After: nach wie vielen Sekunden ein erneuter Versuch erlaubt ist.RateLimit-Limit: insgesamt zulässige Anfragen pro Fenster.RateLimit-Remaining: verbleibendes Anfragekontingent.RateLimit-Reset: verbleibende Zeit, bis der Zähler zurückgesetzt wird.
HTTP/1.1 429 Too Many Requests
Retry-After: 30
RateLimit-Limit: 100
RateLimit-Remaining: 0
RateLimit-Reset: 30
Content-Type: application/json
{"error":"rate_limit_exceeded","message":"Zu viele Anfragen. Versuche es in 30 Sekunden erneut."}
Ermutige Clients, diese Header zu lesen und mit exponentiellem Backoff erneut zu versuchen. So kommt ein abgelehnter Client nicht alle gleichzeitig zurück, sobald das Limit fällt, und erzeugt keine neue Lastwelle.
Praktische Tipps
- Wähle den richtigen Schlüssel: limitiere bei authentifiziertem Verkehr pro Nutzer/API-Schlüssel und bei anonymem Verkehr pro IP. Die IP allein kann hinter einem Proxy oder NAT irreführend sein.
- Gestufte Limits: setze strengere Limits für teure Endpunkte (Suche, Export, Login) und lockerere für leichte Lese-Endpunkte.
- Fail-Open oder Fail-Closed: entscheide bewusst, ob Anfragen abgelehnt oder durchgelassen werden, wenn Redis ausfällt.
- Beobachte: miss deine 429-Rate und welche Schlüssel ans Limit stoßen; unterscheide echten Missbrauch von legitimen Spitzen.
Häufige Fragen
Sollte ich Token Bucket oder Sliding Window verwenden?
Willst du kurze Spitzen zulassen und dabei das Durchschnittstempo wahren, ist Token Bucket ideal und einfach umzusetzen. Musst du das Randverdopplungsproblem absolut beseitigen und sehr genau zählen, wähle das Sliding Window Log. Für die meisten APIs ist Token Bucket mehr als ausreichend.
Wo sollte ich Rate Limiting anwenden?
Idealerweise geschichtet: vorne mit NGINX/CDN/API-Gateway für groben Schutz und in der Anwendungsschicht für feine, geschäftsspezifische Limits. Die Anwendungsschicht kann klügere Entscheidungen treffen, weil sie den Kontext kennt, etwa die Identität des Nutzers.
Was ist der Unterschied zwischen 429 und 503?
429 Too Many Requests sagt aus, dass der Client sein eigenes Limit überschritten hat; die Verantwortung liegt beim Client. 503 Service Unavailable bedeutet, dass der Server allgemein überlastet ist oder gewartet wird. Für Rate Limiting ist 429 immer der richtige Code.
Willst du eine solide Rate-Limiting-Schicht für deine API bauen? Von Token Bucket bis zu Redis-basierten verteilten Zählern entwerfe ich eine Lösung, die zu deiner bestehenden Infrastruktur passt. Kontaktiere mich und lass uns dein Projekt gemeinsam absichern.