Zum Inhalt springen
jproxx
← Zurück zum Blog

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:

DirektiveBedeutung
no-storeDer einzige echte Verzicht aufs Caching: kein Cache — ob privat oder geteilt — darf die Antwort ablegen oder für eine andere Anfrage verwenden.
no-cacheBedeutet 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.
privateSpeicherung 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.
publicErlaubt die Speicherung auch in geteilten Caches und hebt insbesondere die sonst geltende Sperre für Antworten auf Authorization-behaftete Anfragen auf.
s-maxageEigene 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-revalidateEine 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.
immutableSignalisiert, 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 ETag ist eine undurchsichtige Kennung für eine bestimmte Fassung einer Ressource. Der Cache schickt sie später in If-None-Match zurück; ist sie noch aktuell, antwortet der Server mit 304 Not Modified — ohne Rumpf, also ohne die Daten erneut zu übertragen. Ein ETag kann ein starker Validator sein (bytegenaue Gleichheit) oder, mit dem Präfix W/ markiert, ein schwacher (inhaltlich gleichwertig).
  • Last-Modified nennt den letzten Änderungszeitpunkt; der Cache fragt damit über If-Modified-Since nach. Last-Modified gilt 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 auf max-age zurü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.