HTTP Statuscode 304: was er wirklich spart
Eine Antwort mit dem HTTP Statuscode 304 hat keinen Körper. Das ist ihr ganzer Zweck — und zugleich die Quelle des häufigsten Missverständnisses: 304 spart die Übertragung der Datei, nicht die Anfrage danach.
Wer im Wasserfall der Entwicklerwerkzeuge zwanzig grüne 304er sieht und denkt, der Cache arbeite gut, hat die Hälfte richtig gelesen.
304 ist immer eine Antwort auf eine Rückfrage#
Ein Server sendet 304 nie von sich aus. Er sendet ihn nur, wenn die Anfrage bedingt war, wenn der Browser also mitgeteilt hat, welche Fassung er bereits besitzt. Nach RFC 9110 geschieht das über zwei Kopfzeilen:
GET /assets/app.css HTTP/1.1 Host: beispiel.de If-None-Match: "a3f1c9-2b4" If-Modified-Since: Wed, 20 Aug 2026 09:12:44 GMT
Der Server vergleicht. Passt der Validator noch, antwortet er mit 304 Not Modified und ohne Inhalt; der Browser nimmt seine gespeicherte Kopie und schreibt deren Haltbarkeit anhand der mitgeschickten Kopfzeilen fort. Hat sich etwas geändert, kommt eine normale 200-Antwort mit der vollständigen Datei.
Sind beide Bedingungen vorhanden, hat If-None-Match Vorrang. Der ETag ist der genauere Validator; das Datum ist der Notbehelf.
ETag und Last-Modified im Vergleich#
Der ETag ist ein undurchsichtiges Kennzeichen, das der Server selbst vergibt. Die Form W/"..." markiert einen schwachen Validator: Die Fassungen sind gleichwertig, aber nicht Byte für Byte identisch. Für bedingte GET-Anfragen genügt das.
Last-Modified hat eine Auflösung von einer Sekunde. Ändert sich eine Datei zweimal innerhalb derselben Sekunde, kann der Vergleich das nicht bemerken. Dazu kommt ein praktisches Problem: Viele Auslieferungsvorgänge setzen die Änderungszeit aller Dateien neu, obwohl sich inhaltlich nichts getan hat. Dann ist jede Rückfrage eine 200-Antwort.
Wo ETags aus Dateimerkmalen abgeleitet werden, müssen alle Server hinter einem Lastverteiler dieselben Merkmale liefern. Tun sie das nicht, entwertet jeder Knoten die Zwischenspeicher der anderen.
Was gespart wird und was bleibt#
Gespart wird der Körper der Antwort. Bei einem Bild sind das schnell einige hundert Kilobyte, bei einer Stildatei mit wenigen Kilobyte praktisch nichts.
Nicht gespart wird alles davor: die Anfrage selbst, die Wartezeit auf die Antwort, bei einer neuen Verbindung auch Namensauflösung, Verbindungsaufbau und TLS-Aushandlung. Genau darin liegt der Denkfehler. Auf einer schnellen Leitung im Büro kostet eine 304-Antwort kaum etwas. Auf einer Mobilverbindung mit hoher Latenz kostet sie eine volle Rundreise, und dreißig davon nebeneinander kosten dreißigmal die Latenz, auch wenn sie über HTTP/2 parallel laufen.
Eine 304-Antwort ist also der zweitbeste Ausgang. Der beste ist: gar keine Anfrage.
Warum max-age=0, must-revalidate dauernd 304 erzeugt#
Ob überhaupt gefragt wird, entscheidet nicht der 304-Mechanismus, sondern Cache-Control.
max-age=0 bedeutet: Die Antwort ist ab dem Moment ihrer Entstehung veraltet. must-revalidate verbietet zusätzlich, eine veraltete Kopie ohne Rückfrage zu verwenden. no-cache läuft auf dasselbe hinaus — es verbietet nicht das Speichern, sondern das Verwenden ohne Prüfung.
Die Folge ist zwangsläufig: Bei jedem Seitenaufruf geht für jede Datei eine bedingte Anfrage hinaus, und für jede kommt eine 304 zurück. Das ist kein Fehler, sondern exakt das, was konfiguriert wurde. Viele Baukästen und Hoster setzen diese Werte vorsorglich für alles, weil sie damit nie das Problem bekommen, dass jemand eine alte Datei ausgeliefert bekommt.
Damit ist ein seltenes Problem gegen eine dauerhafte Rundreise pro Datei eingetauscht.
Unveränderliche Adressen lösen es an der Wurzel#
Der saubere Ausweg ändert nicht die Haltbarkeit, sondern die Adresse. Jede Datei bekommt einen Abdruck ihres Inhalts in den Namen:
/assets/app.7f3c9a1.css /assets/bundle.2e8b04d.js
Diese Adressen können nie eine andere Fassung bezeichnen, denn ändert sich der Inhalt, ändert sich der Name. Damit ist eine sehr lange Haltbarkeit gefahrlos:
Cache-Control: public, max-age=31536000, immutable
immutable (RFC 8246) unterdrückt die Rückfrage auch dann, wenn der Nutzer die Seite neu lädt. Ergebnis: keine bedingte Anfrage, keine 304, kein Rundreise-Aufschlag.
Das HTML selbst bleibt kurzlebig oder unverbindlich zwischengespeichert. Es ist das Verzeichnis, das die aktuellen Dateinamen nennt — und die einzige Datei, die man deshalb wirklich jedes Mal prüfen muss.
Nachsehen, was tatsächlich passiert#
Erst die Kopfzeilen holen, dann mit dem erhaltenen ETag bedingt nachfragen:
``bash curl -sI https://beispiel.de/assets/app.css curl -sI -H 'If-None-Match: "a3f1c9-2b4"' https://beispiel.de/assets/app.css ``
Kommt beim zweiten Aufruf HTTP/2 304, funktioniert die Revalidierung. Kommt 200 mit vollem Körper, stimmt etwas mit dem Validator nicht.
Im Netzwerkfenster des Browsers ist die Unterscheidung wichtig: „200 (from disk cache)" heißt, es gab keine Anfrage. „304" heißt, es gab eine. Nur die zweite Zeile kostet Zeit. Wie stark sich das auf die Ladezeit auswirkt, zeigt eine Messung der Website-Geschwindigkeit; welcher Kennwert darunter leidet, steht in der Einführung zum Testen der Core Web Vitals.
Kommentare
Gespeichert werden nur dein Name und dein Text — keine E-Mail-Adresse, keine IP-Adresse, kein Cookie. Jeder Kommentar wird vor der Veröffentlichung gelesen; das dauert meist einen Tag. Näheres in der Datenschutzerklärung.