Der Client hat Ihr Limit erreicht. Ihr Server antwortete mit einem nackten 429 — keine Erklärung, kein Hinweis, wann er wiederkommen soll.
Also tat der Client das Einzige, was er konnte: Er wiederholte sofort. Und wieder. Und wieder — denn nichts in Ihrer Antwort sagte ihm etwas anderes.
Sie haben jetzt einen Rate Limiter, der mehr Traffic erzeugt, als er verhindert.

Der Teil, den die Spezifikation bewusst offenlässt
429 Too Many Requests stammt aus RFC 6585 (Nottingham und Fielding, April 2012). Die Definition ist kurz:
„Der Statuscode 429 zeigt an, dass der Nutzer in einer bestimmten Zeitspanne zu viele Requests gesendet hat (‘rate limiting’).“
Dann folgt der Satz, der Ihnen stillschweigend das gesamte Problem überreicht:
„Beachten Sie, dass diese Spezifikation weder definiert, wie der Origin Server den Nutzer identifiziert, noch wie er Requests zählt.“
Beide schwierigen Fragen, absichtlich offengelassen. Wen Sie limitieren und wie Sie zählen — das müssen Sie beantworten, und alles Weitere folgt daraus.
Vier Algorithmen und wo jeder von ihnen bricht
Fixed Window
Requests pro Kalenderintervall zählen. Zu Beginn jeder Minute auf null zurücksetzen.
Ein Zähler und ein Ablauf pro Schlüssel — deshalb fängt jeder hier an. Und deshalb lässt es klammheimlich das Doppelte Ihres Limits durch.
Ein Client, der sein gesamtes Kontingent in der letzten Sekunde eines Fensters verbraucht und es in der ersten Sekunde des nächsten erneut verbraucht, hat in zwei Sekunden das doppelte Limit gesendet — und jeder Request lag im Rahmen der Regeln.
Bei 100 pro Minute liefert ein entschlossener Client 200 in einer gleitenden Minute. Ihre Kapazitätsplanung sagte 100.

Sliding Window Log
Einen Zeitstempel pro Request speichern, alles außerhalb des Fensters verwerfen, den Rest zählen.
Exakt. Und das Teuerste auf dieser Seite: Der Speicherbedarf wächst mit dem Request-Volumen, nicht mit der Anzahl der Clients. Ein einziger stark genutzter Schlüssel kann Tausende Zeitstempel halten, und jede Prüfung läuft sie durch.
Korrekt — und selten den Preis wert.
Sliding Window Counter
Den Zähler des aktuellen und des vorherigen Fensters halten, den alten gewichtet nach der bereits verstrichenen Zeit im aktuellen Fenster. Etwa siebzig Prozent durch eine Minute zählen Sie dreißig Prozent der Requests der Vorminute mit.
Eine Näherung, und eine gute. Konstanter Speicher, keine Grenzkante. Bei extrem stoßartigem Verkehr leicht ungenau — im Tausch gegen zwei Ganzzahlen pro Schlüssel.
Token Bucket
Ein Eimer hält Tokens bis zu einem Maximum. Tokens fließen mit konstanter Rate nach. Jeder Request nimmt eines; ein leerer Eimer bedeutet Ablehnung.
Das ist die Standardwahl, zu der man greifen sollte. Sie erlaubt einen Burst bis zur Eimergröße — was zählt, denn echte Clients sind stoßartig, und ein Limiter, der normales Verhalten bestraft, ist ein Support-Ticket — und hält den langfristigen Durchschnitt bei der Nachfüllrate.
Gespeichert werden zwei Werte: Token-Stand und Zeitpunkt der letzten Auffüllung. Die Nachfüllung berechnen Sie beim Lesen, statt einen Timer laufen zu lassen.
Leaky Bucket
Requests laufen in eine Warteschlange, die mit fester Rate abfließt. Überlauf wird abgelehnt.
Wo Token Bucket Bursts zulässt, glättet Leaky Bucket sie vollständig — der Ausgang ist konstant, egal was ankommt. Richtig, wenn das nachgelagerte System eine Spitze wirklich nicht verkraftet: ein Zahlungsdienstleister, ein Altsystem, eine externe API mit eigenen Limits.
| Algorithmus | Speicher pro Schlüssel | Burst möglich | Hauptschwäche |
|---|---|---|---|
| Fixed Window | 1 Zähler | an Grenzen | bis zu 2× das Limit |
| Sliding Log | 1 Zeitstempel pro Request | nein | Speicher wächst mit Traffic |
| Sliding Counter | 2 Zähler | leicht | näherungsweise |
| Token Bucket | 2 Werte | ja, kontrolliert | Burst-Größe muss justiert werden |
| Leaky Bucket | Warteschlange | nein | erzeugt Latenz |
Die Race Condition, die beim Skalieren auftaucht
Ein Server, ein Zähler im Speicher — kein Problem. Kommt eine zweite Instanz dazu, muss der Zähler in gemeinsamen Speicher. Genau dort sitzt der interessante Fehler:
# KAPUTT — bitte nicht auslieferncount = redis.get(key)if count and int(count) >= LIMIT: return 429redis.incr(key) # ← mehrere Instanzen können hier gleichzeitig seinhandle_request()Zwischen Lesen und Hochzählen liegt ein Fenster. Vier Instanzen mit gleichzeitigen Requests lesen alle 99, schließen alle auf freien Platz und laufen alle durch. Ihr Limit von 100 hat gerade 103 zugelassen.
Die Lösung hat dieselbe Form wie bei der Idempotenz: Machen Sie aus der Entscheidung eine atomare Operation statt Prüfen und dann Handeln.
-- INCR liefert den neuen Wert zurück, das Hochzählen IST also die Prüfunglocal current = redis.call('INCR', KEYS[1])if current == 1 then redis.call('EXPIRE', KEYS[1], ARGV[1])endreturn currentINCR gibt den Wert nach dem Hochzählen zurück — das Inkrement ist die Prüfung, es bleibt keine Lücke. Setzen Sie den Ablauf nur beim Anlegen des Zählers, sonst läuft ein stark genutzter Schlüssel nie ab und sein Fenster wird nie zurückgesetzt.
Zwei Entscheidungen, die bewusst zu treffen sind:
Was passiert, wenn Redis ausfällt? Fail open, und ein Ausfall Ihres Limiters bedeutet unbegrenzten Traffic auf alles dahinter. Fail closed, und ein Limiter-Ausfall legt die API lahm. Keines ist allgemein richtig, aber die Wahl muss bewusst und dokumentiert sein.
Jeder Request kostet nun einen Netzwerk-Roundtrip. Für Hochlast-Stufen ist ein lokales Kontingent im Prozess mit periodischem Abgleich gegen den gemeinsamen Zustand ein verbreitetes Muster — näherungsweise, deutlich schneller, am Edge meist der richtige Handel.
Was Sie tatsächlich zurückgeben sollten
Der Algorithmus entscheidet, ob der Request durchgeht. Die Antwort entscheidet, ob sich der Client danach vernünftig verhält. Das Zweite wiegt schwerer und bekommt weit weniger Aufmerksamkeit.
Der Statuscode
429 ist der vorgesehene Code. RFC 6585 verlangt zweierlei:
„Die Antwortrepräsentationen SOLLTEN Details zur Erläuterung der Situation enthalten und KÖNNEN einen Retry-After-Header enthalten, der angibt, wie lange bis zu einem neuen Request gewartet werden soll.“
Achten Sie auf die Abstufung: Die Erklärung ist ein SHOULD, Retry-After nur ein MAY. In der Praxis senden Sie beides. Ein 429 ohne Retry-After sagt einem Client, dass er gescheitert ist, aber nicht, was er tun soll — und das Standardverhalten der meisten HTTP-Clients ist dann der sofortige nächste Versuch.
Außerdem: 429-Antworten DÜRFEN NICHT gecacht werden. Der RFC ist eindeutig. Ein gecachtes 429 weist einen Client weiter ab, dessen Kontingent vor Minuten zurückgesetzt wurde.
Die Header, die niemand richtig hat
Jetzt der Teil, der die meisten überrascht.
X-RateLimit-Limit, X-RateLimit-Remaining und X-RateLimit-Reset sind kein Standard. Sie waren es nie. Es ist eine Konvention, die sich durch Kopieren verbreitet hat — und weil nichts sie spezifiziert hat, sind sich Implementierungen über ihre Bedeutung uneinig.
Der IETF-Draft, der das beheben soll, dokumentiert den Schaden im eigenen Anhang. X-RateLimit-Remaining bedeutet in freier Wildbahn:
- Sekunden bis zum Reset des Fensters
- Millisekunden bis zum Reset
- einen UNIX-Zeitstempel
- ein formatiertes Datum
Vier unvereinbare Bedeutungen für einen Headernamen. Und die Namen selbst gabeln sich — X-RateLimit-Limit gegen X-Rate-Limit-Limit.
Wie sieht der tatsächliche Vorschlag aus? Kein bisschen so. draft-ietf-httpapi-ratelimit-headers ist aktiv — Version 11, Stand Mai 2026 — und definiert zwei Felder in HTTP-Structured-Field-Syntax:
RateLimit-Policy: "burst";q=100;w=60,"daily";q=1000;w=86400RateLimit: "default";r=50;t=30RateLimit-Policy verkündet die Kontingentregel und bleibt über Antworten hinweg stabil: q ist das Kontingent, w das Fenster in Sekunden. RateLimit meldet den Live-Zustand: r das verbleibende Kontingent, t das wirksame Fenster. Ein Server kann mehrere Regeln gleichzeitig verkünden — ein Burst-Limit und ein Tageslimit — und benennen, auf welche er sich gerade bezieht.
Drei Details dieses Entwurfs lohnen die Übernahme, selbst wenn Sie den Header nie einsetzen:
Sekunden statt Zeitstempel. Der Draft nutzt bewusst delay-seconds: Zeitstempel setzen synchronisierte Uhren zwischen Client und Server voraus, und Uhrabweichungen brechen sie stillschweigend.
Kontingent misst nicht nur Requests. Die Spezifikation definiert drei Einheiten — requests, content-bytes und concurrent-requests. Derselbe Mechanismus begrenzt Bandbreite und Nebenläufigkeit, nicht nur Aufrufzahlen.
Partitionsschlüssel. Ein pk-Parameter sagt dem Client, auf welchen Topf sich die Zahlen beziehen — pro Nutzer, pro Anwendung, pro Ressource. Ohne ihn kann ein Client, der für mehrere Nutzer arbeitet, nicht erkennen, wessen Kontingent er sieht.
Ein ehrlicher Vorbehalt, bevor Sie darauf bauen: Der Draft ist Arbeit im Gange, seine Syntax wurde zwischen Versionen umgebaut, und das jüngste Review des HTTP-Direktorats kam als not ready zurück. Übernehmen Sie die Ideen; warten Sie beim Drahtformat ab.

Maschinenlesbare Gründe
Der Draft registriert außerdem drei Problem Types (RFC 9457), die endlich maschinell unterscheidbar machen, warum gedrosselt wurde:
| Problem Type | Status | Bedeutung |
|---|---|---|
quota-exceeded |
429 | Kontingent aufgebraucht |
temporary-reduced-capacity |
503 | wir drosseln, nicht Sie |
abnormal-usage-detected |
429 | Ihr Traffic-Muster wirkt auffällig |
Der mittlere ist die nützliche Neuerung. „Sie sind über dem Kontingent“ und „wir werfen gerade Last ab“ sind völlig verschiedene Situationen, die sich historisch einen Statuscode teilten — und Clients konnten ein persönliches Limit nicht von einem systemweiten Engpass unterscheiden.
Der Sicherheitsteil, den die meisten Artikel überspringen
Ein höfliches 429 ist trotzdem Arbeit. Die Sicherheitsbetrachtungen von RFC 6585 sind unmissverständlich:
„Wenn ein Server angegriffen wird oder schlicht sehr viele Requests von einer einzelnen Partei erhält, verbraucht die Antwort mit einem 429 auf jeden einzelnen davon Ressourcen. Server sind daher nicht verpflichtet, den Statuscode 429 zu verwenden; zur Begrenzung des Ressourcenverbrauchs kann es angemessener sein, Verbindungen einfach zu verwerfen.“
Rate Limiting ist Fairness zwischen legitimen Clients. Es ist kein DDoS-Schutz. Eine Flut höflich zu beantworten heißt, millionenfach Parsing, Routing und Serialisierung im Auftrag des Angreifers zu erledigen.
Ihr Limiter kann zum Orakel werden. Wenn fehlgeschlagene Authentifizierungsversuche Kontingent verbrauchen, kann ein Angreifer einen Endpunkt abklopfen und aus dem verbleibenden Kontingent auf den Traffic eines fremden Nutzers schließen. Entscheiden Sie ausdrücklich, ob 401 und 403 zählen — und stellen Sie sicher, dass der Partitionsschlüssel nicht vom Aufrufer wählbar ist.
Ehrlichkeit erzeugt Stampeden. Wenn Ihr Kontingent zu einer festen Zeit zurückgesetzt wird und Sie jedem Client wahrheitsgemäß sagen wann, kommen alle Clients in genau diesem Augenblick zurück. Der Draft benennt Problem und Lösung in einem Satz: Fügen Sie dem verkündeten Fenster Jitter hinzu.
Limits sind Hinweise, keine Durchsetzung. Aus dem Sicherheitsteil des Drafts: Drosselung hindert Clients nicht daran, Requests zu senden. Header helfen wohlwollenden Clients zu kooperieren. Gegen die anderen richten sie nichts aus, und der Server braucht weiterhin Mechanismen gegen Ressourcenerschöpfung.
Wie es richtig aussieht
- Der Limitierungsschlüssel ist bewusst gewählt — pro Nutzer, pro API-Key, pro IP — und dokumentiert, denn Clients können keine Regel befolgen, die sie nicht sehen
- Token Bucket, sofern kein konkreter Grund für etwas anderes spricht
- Prüfen und Hochzählen ist atomar; kein Lesen-dann-Schreiben
- Das Verhalten bei Redis-Ausfall ist eine Entscheidung auf Papier, kein Zufall des Codepfads
- Jedes
429trägt eine Erklärung und einRetry-After 429-Antworten werden nie gecacht- Verkündete Reset-Zeiten tragen Jitter
- Ob
4xx-Antworten Kontingent verbrauchen, ist entschieden und nicht geerbt - Etwas Günstigeres als Ihre Anwendung behandelt Missbrauch am Edge
Fazit
Die Wahl zwischen Token Bucket und Sliding Window kostet einen Nachmittag. Welchen Sie nehmen, wird selten das Entscheidende sein.
Entscheidend ist die Antwort. Ein Client, der ein klares Limit, einen Reststand und eine Zeit zum Wiederkommen erhält, taktet sich selbst und hört auf zu hämmern. Ein Client, der ein nacktes 429 erhält, wiederholt in Schleife — und Ihr Limiter hat genau die Last erzeugt, gegen die er eingebaut wurde.
Rate Limiting ist ein Gespräch. Die meisten Implementierungen führen nur eine Seite davon.
Dasselbe Prinzip — dass die Fehlerantwort Teil des Entwurfs ist und kein nachträglicher Gedanke — zieht sich durch eine Million Benachrichtigungen versenden, ohne abzustürzen, wo die Retry-Politik das gesamte System formt.




Aus der Community
Diskussion im Fediverse
Antworten von Mastodon und Bluesky — direkt aus dem offenen Netz, ohne Tracking.
Antworten werden geladen …
Noch keine Antworten. Starte die Diskussion:
Antworten konnten gerade nicht geladen werden.