Der Entwurfsgedanke hinter den fünf Klassen
Die erste Ziffer entscheidet über die Klasse, und das ist Absicht: Auch wenn ein Client einen konkreten Code nicht kennt (etwa das neue 425), behandelt er ihn über die Klasse richtig – jedes 4xx heißt „nicht unverändert wiederholen, das Problem liegt in der Anfrage“, jedes 5xx „später erneut versuchen“, jedes 3xx „Location ansehen“. RFC 9110 verlangt ausdrücklich, dass Clients die Klassensemantik verstehen, einzelne Codes aber nicht kennen müssen. Deshalb gehört auch ein selbst definierter Statuscode in den richtigen Hunderterbereich.
Warum 401 Unauthorized heißt, aber „nicht authentifiziert“ meint
Ein historischer Benennungsfehler. Frühes HTTP unterschied authentication (wer du bist) nicht von authorization (was du darfst); 401 bekam den Namen Unauthorized, doch der begleitende WWW-Authenticate-Header und die Semantik „nach erneuter Angabe von Zugangsdaten kann es klappen“ sprechen beide von Authentifizierung. RFC 9110 formuliert in §15.5.2 inzwischen "lacks valid authentication credentials". Ein Satz zum Merken: 401 = bitte anmelden, 403 = angemeldet reicht auch nicht. Das Frontend entscheidet danach, ob es zur Login-Seite springt oder „keine Berechtigung“ zeigt; falsch herum eingesetzt landen angemeldete Nutzer immer wieder auf der Login-Seite.
Die zwei Achsen der Weiterleitung: dauerhaft / temporär, Methode erhalten oder nicht
Die vier gängigen Redirect-Codes sind eine 2×2-Matrix. 301 und 308 sind dauerhaft (Browser und Suchmaschinen merken sich die neue Adresse), 302 und 307 sind temporär. Historisch machten Browser aus einem POST bei 301/302 ein GET; RFC 9110 erkennt diese Realität an und bietet mit 307/308 die strikten Varianten, bei denen „Methode und Request-Body erhalten bleiben müssen“. Für einen https-Redirect einer Website ist 301 in Ordnung; bei der Migration eines API-Endpunkts muss ein POST 307/308 verwenden, sonst geht der Request-Body verloren.
Caching und 304
304 wird oft für „eine Anfrage gespart“ gehalten. Tatsächlich hat der Browser trotzdem eine bedingte Anfrage mit If-None-Match / If-Modified-Since geschickt, der Server hat verglichen und mit 304 ohne Response-Body geantwortet – gespart wird die Übertragung, nicht der Roundtrip. Anfragen wirklich sparen lässt sich mit Cache-Control: max-age (innerhalb der Gültigkeit fragt der Browser den Server gar nicht); beide greifen zusammen: während max-age läuft, direkt verwenden, nach Ablauf per bedingter Anfrage mit 304 verlängern. In den DevTools ist "200 (from disk cache)" das Erste und "304" das Zweite.
Die Gateway-Drillinge 502 / 503 / 504
Die häufigste 5xx-Familie im Livebetrieb; sie auseinanderzuhalten grenzt die Fehlersuche sofort ein. Ein Reverse Proxy (Nginx, Gateway, CDN) erreicht das Upstream nicht oder das Upstream lieferte etwas Ungültiges – 502, also nachsehen, ob der Anwendungsprozess abgestürzt ist; das Upstream sagt ausdrücklich, dass es ausgelastet ist oder gewartet wird – 503, meist bewusst zurückgegeben, also Healthchecks und Circuit Breaker ansehen; das Upstream antwortet nicht – 504, also Slow Queries und Timeout-Konfiguration prüfen. Cloudflare teilt diese drei Fälle in 520–526 auf, wobei 521 ≈ 502 (Verbindung abgelehnt), 522/524 ≈ 504 (Verbindungs- / Lesetimeout) und 525/526 Probleme auf TLS-Ebene sind.
Statuscodes und Business-Codes müssen zusammenpassen
Das häufigste Anti-Muster: alle Anfragen mit 200 beantworten und den Fehler in ein code-Feld des Response-Bodys legen. Das lässt CDNs Fehlerseiten cachen, macht 5xx-Alarme im Monitoring wirkungslos, entwertet das fetch().ok des Browsers und lässt Retry-Strategien ohne Anhaltspunkt. Richtig ist die Aufteilung: Der HTTP-Statuscode sagt, was auf Anfrageebene passiert ist (Semantik nach RFC 9110), der Business-Code im Body beschreibt das fachliche Detail – die beiden teilen sich die Arbeit, statt einander zu ersetzen: Zu wenig Bestand ist 422 + {"code":"OUT_OF_STOCK"}, nicht 200 + {"code":500}.