Cache-Control einfach erklärt: Was no-cache, no-store, max-age und s-maxage jeweils bewirken

Inhalt
Eine Seite, die beim zweiten Besuch sofort da ist, wurde nicht noch einmal geholt, sondern kam aus einem Speicher irgendwo zwischen Server und Bildschirm – und ein Header hat das erlaubt.
Diese Zeile heißt Cache-Control – kurz, gebaut aus Wörtern, die selbsterklärend aussehen, und zwei davon bedeuten beinahe Gegensätzliches, obwohl sie wie Synonyme klingen.

Zwei Speicher, nicht einer
Zwischen Server und Bildschirm gibt es meist zwei Orte, an denen eine Antwort liegen bleiben kann.
Der erste ist der Browser auf einem Gerät. Was dort liegt, dient genau einer Person und bleibt, bis es abläuft oder gelöscht wird.
Der zweite ist ein gemeinsamer Speicher: ein CDN, ein Firmen-Proxy, irgendetwas im Weg, das viele Menschen aus einer Kopie bedient. Er ist der wirksamere von beiden, weil eine gespeicherte Kopie tausende Anfragen spart – und der gefährlichere, weil eine persönliche Seite, die dort landet, an die Falschen ausgegeben wird.
Fast jedes Missverständnis übers Zwischenspeichern kommt daher, dass der zweite Speicher vergessen wird. Angaben, die sich zu widersprechen scheinen, sprechen meist verschiedene Speicher an.
no-cache und no-store sind nicht zweimal dasselbe Wort
Die beiden sehen aus wie ein Paar und sind keins.
no-store heißt: nichts aufbewahren. Keine Kopie irgendwo, weder im Browser noch in einem gemeinsamen Speicher noch auf einer Platte. Jede Anfrage geht zum Server zurück. Das ist die Angabe für einen Kontoauszug.
no-cache heißt: aufbewahren, aber vor der Verwendung nachfragen. Die Kopie wird ganz normal gespeichert; der Browser darf sie nur nicht ohne Rückfrage ausliefern. Antwortet der Server, dass sich nichts geändert hat, wird die gespeicherte Kopie verwendet, und es fließt kein Inhalt.
no-cache bedeutet also nicht „nicht zwischenspeichern”. Es bedeutet „zwischenspeichern, aber jedes Mal nachfragen” – und es gibt diese Angabe gerade deshalb, um Zwischenspeichern für Seiten möglich zu machen, die sich unvorhersehbar ändern. Eine kurze Rückfrage kostet einen Bruchteil einer vollen Übertragung.
Wer no-store schreibt und no-cache meint, verschenkt diese Ersparnis bei jeder einzelnen Anfrage. Wer no-cache schreibt und no-store meint, hinterlässt eine Kopie von etwas Vertraulichem auf einer Platte.
Zwei Zahlen für zwei Speicher
max-age ist die Lebensdauer in Sekunden. s-maxage ist dasselbe, aber nur für gemeinsame Speicher, und dort sticht es max-age aus.
Die Startseite von Wikipedia zeigt, wozu es beide gibt. Der Header dort lautet s-maxage=86400, must-revalidate, max-age=3600: das CDN darf die Seite einen ganzen Tag behalten, ein Browser eine Stunde. Der teure Speicher hält sie lange, der persönliche kurz – und eine Zeile regelt beides.
Ohne s-maxage folgt der gemeinsame Speicher schlicht dem max-age. Das ist der Normalfall, und deshalb hält ein CDN eine Seite manchmal weit länger oder kürzer als gedacht: für ihn wurde keine eigene Zahl geschrieben.
must-revalidate vervollständigt das Bild. Ist die Lebensdauer abgelaufen, darf die gespeicherte Kopie nicht mehr verwendet werden, auch nicht kurz, auch nicht bei unerreichbarem Server. Ohne diese Angabe hat ein Speicher mit einer abgelaufenen Kopie einen gewissen Spielraum.

Wer überhaupt speichern darf
private und public beschreiben nicht den Inhalt, sondern benennen den Speicher.
private heißt: nur der Browser einer Person. Gemeinsame Speicher dürfen diese Antwort nicht behalten. Das ist die richtige Einstellung für alles, was einen Namen, einen Warenkorb oder einen Kontostand zeigt – alles, was für einen einzelnen Besuch zusammengesetzt wurde.
public heißt: gemeinsame Speicher dürfen ebenfalls behalten. Es hebt außerdem ein paar Regeln auf, die sonst gegen das Zwischenspeichern sprächen, und steht deshalb auch auf Seiten, die ohnehin problemlos speicherbar wären.
Der Fehler ist hier leise und ernst: eine persönliche Seite ohne private, hinter einem CDN, wird einmal gespeichert und dem nächsten Besuch samt fremdem Namen vorgesetzt. Nichts stürzt ab, nichts wird protokolliert, und die Seite sieht richtig aus – für die falsche Person.
Die veralteten Antworten, die mit Absicht ausgeliefert werden
Zwei Zusätze erlauben einem Speicher, eine abgelaufene Kopie bewusst auszuliefern, und beide lassen Seiten schneller wirken, als sie sind.
stale-while-revalidate sagt: ist die Kopie gerade eben abgelaufen, gib sie trotzdem heraus und hol im Hintergrund eine frische. Der Besuch wartet auf nichts; der nächste bekommt die neue Fassung.
stale-if-error sagt: ist der Server nicht erreichbar, liefere lieber die alte Kopie als eine Fehlerseite. Eine Seite mit dieser Angabe übersteht einen kurzen Ausfall, ohne dass es jemand bemerkt.
Die Startseite der BBC verwendet beides, mit 30 und 90 Sekunden. GitHub schickt dagegen max-age=0, private, must-revalidate – nichts wird ohne Rückfrage ausgeliefert, denn eine Repository-Seite darf keine Sekunde veraltet sein.
Keines von beidem ist richtiger. Es sind verschiedene Antworten auf dieselbe Frage: was ist hier schlimmer, eine langsame Seite oder eine alte.
Was ein Header zeigt
Die Zeile kommt mit jeder Antwort mit, und sie zu lesen kostet eine Anfrage.
Vier Fragen lohnen sich. Ist no-store gemeint oder no-cache – nichts behalten, oder behalten und nachfragen. Trägt eine persönliche Seite ein private. Gibt es ein eigenes s-maxage für den gemeinsamen Speicher, oder erledigt eine Zahl stillschweigend beide Aufgaben. Und ist die Lebensdauer plausibel dafür, wie oft sich der Inhalt tatsächlich ändert.
Der HTTP-Security-Header-Prüfer liest die Header einer beliebigen Adresse so, wie ein Browser sie liest, und verfolgt dabei die Weiterleitungen.
Die brauchbare Antwort ist ein Satz: ob diese Seite dort gespeichert wird, wo sie soll, und so lange, wie sie soll – und von niemandem sonst.
Fragen und Antworten
Warum spart no-cache nur dann Übertragung, wenn die Antwort einen ETag oder ein Last-Modified trägt?
Weil die Rückfrage einen Vergleichswert braucht. Der Browser schickt ihn als If-None-Match oder If-Modified-Since mit, und nur dann kann der Server mit 304 Not Modified antworten, ohne den Inhalt erneut zu senden. Fehlen beide Angaben, gibt es nichts zu vergleichen, und jede Rückfrage wird zu einer vollen Übertragung.
Wie verschwindet eine veraltete Seite aus einem CDN, das sie mit s-maxage=86400 hält?
Nicht durch einen neuen Header. Die Kopien, die schon gespeichert sind, tragen ihre Lebensdauer mit sich; ein geänderter Header gilt erst für die nächste Kopie. Beim CDN gibt es dafür das Leeren des Caches über dessen Verwaltung oder Schnittstelle, meist Purge genannt.
Bei Browsern gibt es dieses Mittel nicht. Eine Kopie mit max-age=3600 bleibt dort bis zu eine Stunde, gleich was der Server inzwischen sendet. Das passt zur Aufteilung im Beispiel von Wikipedia: Der Speicher, den der Betreiber leeren kann, darf die Seite lange halten, der, den er nicht erreicht, nur kurz.
Für Skripte und Stylesheets ist der übliche Weg ein anderer: eine neue Adresse je Fassung, etwa mit einer Versionsnummer oder Prüfsumme im Dateinamen, und dazu eine lange Lebensdauer. Eine geänderte Datei hat dann eine neue Adresse, und keine alte Kopie steht im Weg.
Welche Regel hebt public auf, und wann wird das heikel?
Die wichtigste betrifft Anfragen mit einem Authorization-Header, also etwa hinter einem Passwortschutz per HTTP-Anmeldung. Die Antwort auf eine solche Anfrage darf ein gemeinsamer Speicher nach RFC 9111 normalerweise nicht für weitere Anfragen verwenden, weil sie für eine angemeldete Person bestimmt ist. public erlaubt es ausdrücklich, ebenso s-maxage und must-revalidate.
Heikel wird das, wenn eine Vorlage public für alle Seiten setzt und ein geschützter Bereich darunterfällt: Das CDN darf die Antwort dann speichern und an Besuche ohne Anmeldung ausliefern. Für geschützte Bereiche gehört private oder no-store in den Header, unabhängig davon, was die übrige Seite sendet.
Greift stale-if-error auch, wenn der Server zwar antwortet, aber mit einem Fehler?
Ja. Nach RFC 5861 zählen auch die Statuscodes 500, 502, 503 und 504 als Fehler, nicht nur ein unerreichbarer Server. Ob ein Speicher die Angabe überhaupt beachtet, hängt allerdings vom CDN und vom Browser ab, und eine Fehlerseite mit Status 200 erkennt keiner von beiden als Fehler.
Verstehen durch Ausprobieren
Wann muss der Browser wieder nachfragen?
Verändere das Alter der Antwort. Wechsle danach zu no-cache oder no-store.
Modell einer speicherbaren GET-Antwort mit ETag. Alter enthält die Transportzeit. Gleiche URL und Vary-Werte, keine anderen Direktiven, kein Offline-Betrieb.
Dieses Modul rechnet im Browser und sendet oder speichert Deine Eingaben nicht.
Verstehen durch Ausprobieren
Eine Minute: Findest Du den Denkfehler?
Cache-Control: no-cache. Was darf ein Cache tun?
no-cache erlaubt Speicherung, verlangt aber erfolgreiche Validierung vor Wiederverwendung. no-store verbietet die Speicherung.