Webserver-Caching verstehen: HTTP-Caching von Cache-Control bis Revalidierung
1. Juli 2026 · jproxx
Die schnellste Anfrage ist die, die nie über das Netzwerk gestellt wird. Im reinsten Fall sorgt dafür der Browser selbst: Er hält eine bereits heruntergeladene Antwort in seinem eigenen Cache vor, und ruft der Nutzer dieselbe Ressource erneut auf, während die gespeicherte Fassung noch gültig ist, bedient der Browser sie direkt aus dem lokalen Speicher — es verlässt buchstäblich keine Anfrage das Gerät. Das ist der Kern von HTTP-Caching. Weiter außen liegende, geteilte Caches wie Proxy, Reverse-Proxy oder CDN verkürzen anschließend nur noch den Weg zum Inhalt, statt ihn ganz einzusparen. So oder so spart Caching Latenz, Bandbreite, Serverlast und damit bares Geld. Der HTTP-Standard beschreibt einen Cache in RFC 9111 als „lokalen Speicher von Antwortnachrichten samt der Steuerung, die diese Nachrichten ablegt, hervorholt und löscht”. Klingt simpel — die Tücken stecken in den Details, und einige davon werden regelmäßig falsch verstanden. Dieser Beitrag ordnet die Bausteine so, wie man sie im Betrieb tatsächlich braucht.
Wo Caches sitzen
Caches gibt es auf jeder Ebene der Verbindung zwischen Browser und Ursprungsserver, und
sie zerfallen in zwei grundsätzliche Klassen. Ein privater Cache gehört genau einem
Nutzer — typischerweise der Cache im Browser. Weil seine Inhalte niemand anderes zu
sehen bekommt, darf er auch personalisierte, auf einen Benutzer zugeschnittene Antworten
vorhalten. Ein geteilter Cache (shared cache) dagegen bedient viele Nutzer und sitzt
als Zwischenstation im Verbindungsweg: als Weiterleitungs-Proxy in Firmen- oder
Providernetzen, als Reverse-Proxy direkt vor der Anwendung (etwa Varnish, nginx oder
Apache mit mod_cache) oder als weltweit verteilter CDN-Knoten nahe am Nutzer.
Diese Unterscheidung ist keine Formsache, sondern verändert das Verhalten. Ein geteilter
Cache darf eine als private markierte Antwort nicht speichern, und für Antworten auf
Anfragen mit Authorization-Header gelten zusätzliche Einschränkungen. Auch mehrere
Direktiven richten sich ausschließlich an geteilte Caches. Wer das übersieht, riskiert
im harmlosen Fall eine wirkungslose Konfiguration — und im schlimmen Fall, dass ein
CDN eine personalisierte Seite eines Nutzers an alle anderen ausliefert.
Vom hier beschriebenen HTTP-Caching zu trennen ist das serverseitige Caching innerhalb der Anwendung — etwa ein Seiten-Cache, ein Objekt-Cache (häufig mit Redis) oder der Opcode-Cache von PHP. Diese Schichten entlasten den Ursprungsserver, bevor überhaupt eine HTTP-Antwort entsteht; die Regeln, um die es im Folgenden geht, greifen erst danach, auf der bereits erzeugten Antwort.
Frisch oder abgelaufen: das Freshness-Modell
Der Kern des HTTP-Cachings ist ein Ablaufmodell. Jede gespeicherte Antwort hat eine Frische-Lebensdauer; solange sie nicht überschritten ist, gilt die Antwort als frisch und darf ohne Rückfrage beim Ursprung ausgeliefert werden. Danach gilt sie als abgelaufen (stale) und muss in der Regel erst überprüft werden.
Die Lebensdauer setzt man am zuverlässigsten über Cache-Control: max-age=<Sekunden>.
Hier lauert bereits ein verbreiteter Irrtum: max-age zählt nicht ab dem Zeitpunkt,
zu dem der Cache die Antwort empfangen hat, sondern ab dem Zeitpunkt, zu dem der Ursprung
die Antwort erzeugt hat. Wie alt eine gespeicherte Antwort bereits ist, verrät der
Age-Header, den geteilte Caches mitsenden; er beziffert die geschätzte Zeit seit
Erzeugung oder letzter erfolgreicher Überprüfung am Ursprung. Der ältere Expires-Header
nennt statt einer Dauer einen absoluten Ablaufzeitpunkt und gilt heute als überholt —
liegt beides vor, gewinnt max-age.
Fehlt jede explizite Angabe, ist die Antwort damit nicht automatisch unspeicherbar:
HTTP ist darauf ausgelegt, so viel wie möglich zu cachen, und Caches dürfen dann eine
heuristische Frische schätzen. Üblich sind rund zehn Prozent der Zeitspanne zwischen
dem Last-Modified-Datum und dem Erzeugungsdatum der Antwort — eine vor einem Jahr
zuletzt geänderte Datei würde so etwa 36 Tage als frisch gelten. Genau deshalb ist es
riskant, Caching dem Zufall zu überlassen: Ohne bewusste Header entscheidet die
Heuristik, nicht der Betreiber.
Cache-Control richtig setzen
Cache-Control ist der zentrale Hebel. Die wichtigsten Antwort-Direktiven:
| Direktive | Bedeutung |
|---|---|
no-store | Der einzige echte Verzicht aufs Caching: kein Cache — ob privat oder geteilt — darf die Antwort ablegen oder für eine andere Anfrage verwenden. |
no-cache | Bedeutet nicht „nicht speichern”: Die Antwort darf gespeichert werden, muss aber vor jeder Wiederverwendung erneut beim Ursprung überprüft werden. Für einen echten Verzicht braucht man no-store. |
private | Speicherung nur in einem privaten Cache; ein geteilter Cache muss die Antwort verwerfen. Keine Vertraulichkeitsgarantie — die Nutzdaten bleiben für jeden lesbar, der Zugriff auf den privaten Cache hat. |
public | Erlaubt die Speicherung auch in geteilten Caches und hebt insbesondere die sonst geltende Sperre für Antworten auf Authorization-behaftete Anfragen auf. |
s-maxage | Eigene Lebensdauer ausschließlich für geteilte Caches; überschreibt dort max-age und Expires (private Caches ignorieren die Direktive) und übernimmt laut RFC 9111 die Semantik von proxy-revalidate. |
must-revalidate | Eine abgelaufene Antwort muss vor der Wiederverwendung erfolgreich überprüft werden; ist der Ursprung nicht erreichbar, muss der Cache eine Fehlerantwort erzeugen (empfohlen: Status 504) statt die veraltete Antwort auszuliefern. |
immutable | Signalisiert, dass sich die Antwort während ihrer Frische garantiert nicht ändert — der Cache soll dann selbst beim manuellen Neuladen keine Überprüfung anstoßen. |
Revalidierung: ETag und Last-Modified
Ist eine Antwort abgelaufen, muss sie nicht zwangsläufig neu übertragen werden. Über eine bedingte Anfrage kann der Cache beim Ursprung nachfragen, ob sich überhaupt etwas geändert hat. Dazu dienen zwei Validatoren, die der Ursprung mit der ursprünglichen Antwort mitgibt:
- Der
ETagist eine undurchsichtige Kennung für eine bestimmte Fassung einer Ressource. Der Cache schickt sie später inIf-None-Matchzurück; ist sie noch aktuell, antwortet der Server mit304 Not Modified— ohne Rumpf, also ohne die Daten erneut zu übertragen. Ein ETag kann ein starker Validator sein (bytegenaue Gleichheit) oder, mit dem PräfixW/markiert, ein schwacher (inhaltlich gleichwertig). Last-Modifiednennt den letzten Änderungszeitpunkt; der Cache fragt damit überIf-Modified-Sincenach.Last-Modifiedgilt von Natur aus als schwacher Validator — seine Sekundenauflösung und die Möglichkeit inhaltsgleicher Änderungen machen es ungenauer als einen starken ETag. Für exakte Vergleiche (etwa Bereichs-Anfragen) ist deshalb ein ETag nötig.
Ein bekannter Fallstrick betrifft ETags hinter einem Load Balancer: Apache bezog früher
die Inode-Nummer der Datei in den ETag ein (Standard bis httpd 2.3.14), sodass dieselbe
Datei auf verschiedenen Backend-Servern unterschiedliche ETags erhielt und die
Revalidierung ins Leere lief. Seit httpd 2.3.15 ist der Inode aus dem Standard entfernt;
wer ihn dennoch explizit aktiviert hat, sollte FileETag MTime Size setzen.
Der Vary-Header: Cache-Schlüssel mit Bedacht
Ein Cache ordnet eine gespeicherte Antwort einer Anfrage über deren URL zu. Reicht die
URL nicht aus, weil der Server je nach Anfrage-Header unterschiedliche Fassungen
ausliefert, benennt der Vary-Header die maßgeblichen Header. Sinnvoll ist das etwa bei
Vary: Accept-Encoding (komprimierte und unkomprimierte Fassung) oder Vary: Accept-Language.
Gefährlich wird es, wenn stark variierende Header in den Schlüssel geraten. Vary: Cookie
oder Vary: User-Agent zersplittern den Cache in unzählige Varianten, sodass er praktisch
nie einen Treffer landet — die Trefferquote sinkt gegen null, und der Cache verliert
seinen Zweck. Vary: * schließlich signalisiert, dass die Antwort von Faktoren außerhalb
der Anfrage-Header abhängt, und macht sie effektiv unspeicherbar.
Moderne Direktiven für Tempo und Ausfallsicherheit
Zwei Erweiterungen aus RFC 5861 verbessern das gefühlte Tempo und die Robustheit:
stale-while-revalidate=<Sekunden>erlaubt dem Cache, eine gerade abgelaufene Antwort sofort auszuliefern und die Überprüfung im Hintergrund nachzuholen. Der Nutzer wartet nicht auf den Ursprung; die nächste Anfrage erhält die aufgefrischte Fassung. Dies ist ausschließlich eine Antwort-Direktive; Browser ohne Unterstützung fallen stillschweigend aufmax-agezurück, es geht also nichts kaputt.stale-if-error=<Sekunden>erlaubt, im Fehlerfall eine abgelaufene Antwort weiterzureichen, wenn der Ursprung mit 500, 502, 503 oder 504 antwortet oder der Fehler lokal entsteht. So bleibt eine Seite erreichbar, während das Backend hakt.
Eine praxistaugliche Strategie
Aus alldem ergibt sich ein bewährtes Muster, das sich an der Art des Inhalts orientiert.
Statische Dateien mit Fingerabdruck — also CSS, JavaScript und Bilder, deren
Dateiname einen Hash des Inhalts enthält (app.9f3c1a.js) — kann man bedenkenlos maximal
lange cachen, weil sich bei jeder Änderung der Dateiname ändert und damit automatisch eine
neue Adresse angefragt wird:
Cache-Control: public, max-age=31536000, immutable
HTML-Dokumente dagegen ändern sich unter gleichbleibender Adresse. Hier bewährt sich,
die Antwort speichern zu lassen, aber vor jeder Nutzung zu überprüfen — in Kombination mit
einem ETag kostet das im unveränderten Fall nur eine schlanke 304-Antwort statt der
vollen Seite:
Cache-Control: no-cache
ETag: "a1b2c3"
Geschützte, personalisierte Antworten gehören niemals in einen geteilten Cache. Für
streng vertrauliche Daten ist no-store richtig; für benutzerspezifische, aber im Browser
zwischenspeicherbare Inhalte private:
Cache-Control: private, no-store
Zwei Grundsätze runden das ab. Erstens die alte Weisheit, dass das Invalidieren von Caches
zu den schwierigen Problemen der Informatik zählt — der Fingerabdruck im Dateinamen umgeht
es für statische Dateien elegant, weil neue Inhalte schlicht eine neue Adresse bekommen.
Zweitens die Sicherheit: private verhindert nur die Ablage in geteilten Caches, ersetzt
aber keine Verschlüsselung und keine Zugriffskontrolle; wirklich sensible Antworten steuert
man mit no-store.
Wie wir das handhaben
Richtig gesetzte Cache-Header sind unspektakulär, aber sie entscheiden spürbar über Ladezeit und Serverlast. In unserem Managed-Hosting konfigurieren wir diese Ebenen — Reverse-Proxy, Header-Regeln und die Trennung von langlebigen Assets und stets frischem HTML — als Teil des Setups, damit Websites schnell bleiben, ohne dass veraltete Inhalte ausgeliefert werden.
Quellen: RFC 9111 — HTTP Caching · RFC 9110 — HTTP Semantics · RFC 5861 — stale-while-revalidate / stale-if-error · MDN — HTTP Caching · MDN — Cache-Control · web.dev — HTTP caching
Fragen zur Performance Ihrer Website? Sprechen Sie uns an.