HTTP Conditional Caching mit ETag und Last-Modified verstehen

HTTP Caching spart Zeit und Daten, indem ich Ressourcen nur dann erneut lade, wenn sie sich tatsächlich geändert haben. Über ETag und Last-Modified prüfe ich per bedingter Anfrage, ob der Server mit 304 Not Modified antwortet, wodurch Übertragung und Serverarbeit deutlich sinken.

Zentrale Punkte

Die folgenden Kernaussagen zeigen, worauf ich beim Conditional Caching mit ETag und Last-Modified achte.

  • Weniger Traffic: Unveränderte Dateien liefern 304, nicht den ganzen Body – das senkt Daten und Latenz spürbar.
  • Bessere Performance: Kürzere Wartezeiten fördern UX und Core Web Vitals, was SEO hilft.
  • Zwei Mechanismen: Last-Modified/If-Modified-Since und ETag/If-None-Match validieren den Cache sicher.
  • Cache-Control: Direktiven steuern Frische, Revalidierung und Verhalten in Zwischen-Caches.
  • Kombination: Beide Verfahren zusammen bieten hohe Genauigkeit und einfache Fallbacks.

Ich prüfe zuerst, welche Ressourcen wirklich oft wechseln und welche selten. Für selten geänderte Dateien setze ich einen Last-Modified-Zeitpunkt und ergänze einen ETag. Für dynamische Antworten nutze ich bevorzugt den ETag, weil jede inhaltliche Änderung sofort erkennbar wird. So entlaste ich Server, reduziere Latenzen und liefere wiederkehrenden Besuchern sehr schnelle Seiten. Diese Strategie stärkt die Core Web Vitals und damit indirekt die Sichtbarkeit.

HTTP Conditional Caching: So prüfe ich Gültigkeit

Beim erneuten Abruf sendet der Client neben GET zusätzliche Header, die ich serverseitig auswerte. Trägt die Ressource denselben ETag wie im Cache (If-None-Match), liefere ich 304 Not Modified ohne Body. Hat sich am Zeitstempel nichts geändert (If-Modified-Since), reagiert der Server ebenso mit 304. Stimmen Tag oder Datum nicht mehr, schicke ich 200 OK mit neuem Inhalt plus aktualisiertem Last-Modified und ETag. Dadurch spare ich Bandbreite, halte den Cache aktuell und sorge für spürbar schnellere Ladezeiten.

Last-Modified und If-Modified-Since im Alltag

Den Header Last-Modified setze ich auf den echten Änderungszeitpunkt der Datei, etwa aus dem Dateisystem. Kommt später ein Request mit If-Modified-Since und der Ressource hat sich seitdem nicht geändert, antworte ich mit 304. Dieser Weg ist unkompliziert, gut nachvollziehbar und ideal für statische Assets wie CSS, JS oder Bilder. Grenzen zeigt das Sekundenraster von HTTP-Zeitstempeln und Situationen, in denen sich Inhalte logisch ändern, ohne dass ein sauberer Dateizeitpunkt existiert. Wo Last-Modified an seine Grenzen stößt, ergänzt ein ETag die Kontrolle.

ETag und If-None-Match in dynamischen Systemen

Einen ETag generiere ich als Hash, Versions-ID oder aus einer Datenbankspalte, die Zustandswechsel markiert. Bei erneutem Zugriff schickt der Browser If-None-Match, ich vergleiche den Tag mit meinem aktuellen Wert und antworte entsprechend mit 304 oder 200. Dieser Abgleich erkennt jede sinnvolle Inhaltsänderung, ohne sich auf Dateizeitstempel zu verlassen. Gerade bei APIs, zusammengesetzten Seiten oder personalisierten Fragmenten liefert das sehr exakte Ergebnisse. Wichtig bleibt, dass ich ETags in Cluster-Umgebungen konsistent halte, damit kein Server zufällig einen anderen Tag erzeugt.

Cache-Control richtig kombinieren

Mit Cache-Control definiere ich, wie lange Inhalte ohne Rückfrage als frisch gelten und wann der Browser revalidiert. Ich setze passende max-age-Werte je nach Änderungsfrequenz und nutze must-revalidate, wenn veraltete Daten kritisch wären. Für versionierte Dateien eignet sich eine lange Gültigkeit, während häufig veränderliche Antworten kürzer leben und sich dann über ETag oder Datum sauber prüfen lassen. So verbinde ich kurze Antwortzeiten mit korrekter Aktualität. Wer sich tiefer einlesen will, findet viele Muster unter Cache-Control Strategien, die ich in der Praxis nutze.

Ablauf eines Conditional GET Schritt für Schritt

Beim ersten Abruf sendet der Server 200 OK mit Cache-Control, Last-Modified und ETag, der Browser speichert alles. Beim nächsten Besuch entscheidet das Alter im Cache, ob eine Revalidierung nötig ist. Ist diese fällig, fragt der Browser mit If-None-Match und/oder If-Modified-Since an. Stimmen Werte mit dem aktuellen Zustand überein, schicke ich 304 Not Modified, der Client nutzt seinen Cache weiter. Passen sie nicht mehr, folgt 200 OK mit neuem Body und aktualisierten Validierungsdaten.

Vergleich: ETag vs. Last-Modified

Beide Verfahren sichern mir Kontrolle, unterscheiden sich jedoch in Aufwand, Genauigkeit und Eignung. Last-Modified punktet durch einfache Umsetzung und klare Semantik, solange ich saubere Zeitstempel habe. Der ETag bildet Inhalte sehr genau ab, kostet aber etwas Logik zur Erzeugung. In vielen Setups kombiniere ich beides und profitiere so von Einfachheit plus exakter Erkennung. Die folgende Tabelle fasst typische Eigenschaften zusammen und hilft bei der Entscheidung.

Aspekt Last-Modified ETag Hinweis
Identität Zeitstempel der letzten Änderung Inhalts-Hash oder Versions-ID Zeit vs. inhaltsbasierte Kennung
Änderungserkennung Sekundenauflösung, indirekt Direkt am Inhalt orientiert ETag erkennt kleinste Diffs
Implementierung Sehr leicht, Dateisystem reicht Erfordert Generierung und Konsistenz Cluster brauchen gleiche ETags
Einsatz Statische Assets Dynamische Antworten Kombination deckt viele Fälle ab
Antworten 304 bei unverändertem Timestamp 304 bei identischem Tag 200 bei Änderungen mit neuem Wert

Praxis: Statische Assets effizient ausliefern

Statische Dateien wie CSS, JS und Bilder ändern sich selten und eignen sich für lange max-age-Zeiten. Für versionierte Dateien setze ich hohe Werte bis zu einem Jahr und markiere sie als immutable, damit der Browser ohne Rückfragen lädt. Für nicht versionierte Assets wähle ich kürzere Fristen und verlasse mich auf Revalidierung über ETag und Last-Modified. So vermeide ich veraltete Inhalte und halte den Traffic niedrig. Achte ich darauf, keine Cache-Header sabotieren zu lassen, erreiche ich eine hohe Trefferquote im Cache.

Praxis: APIs und dynamische Seiten

Bei APIs setze ich meist auf ETags, die ich aus dem serialisierten Ergebnis oder einer Versionsspalte bilde. Ändert sich der Datensatz, generiere ich einen neuen Tag, Clients erkennen das sofort. Für Inhalte mit unsicherem Zeitstempel verzichte ich oft auf Last-Modified, damit kein falscher Eindruck von Frische entsteht. Ergänzend steuere ich die Lebensdauer über Cache-Control und zwinge nach Ablauf zur Revalidierung. So halte ich Daten verlässlich frisch, ohne Antworten unnötig groß zu machen.

Testing und Monitoring für Cache-Hitrate

Ich kontrolliere Header wie ETag, Last-Modified, If-None-Match und If-Modified-Since in den Developer-Tools. Dabei achte ich auf die Response-Codes, insbesondere 304 vs. 200, um die Effektivität meiner Revalidierung zu sehen. Trifft 304 selten, justiere ich Cache-Control, Gültigkeitsdauern und die ETag-Erzeugung. Logs und Metriken zeigen mir, welche Pfade unnötig groß antworten. Für gebündelte Verbesserungen nutze ich gerne ein Conditional-Requests Paket, das Konfiguration und Tests zusammenführt.

Hosting-Architektur und ETag-Fallen

In Multi-Server-Setups muss ein ETag unabhängig von der Instanz sein, sonst bricht die Wiedererkennung. Ich sorge dafür, dass alle Knoten dieselbe Logik und denselben Schlüssel für die Erzeugung verwenden. Reverse Proxies oder CDNs dürfen ETags nicht verändern und sollten Konditions-Header korrekt weiterreichen. Bei Deployments mit Asset-Fingerprints vermeide ich serverseitige ETag-Neuberechnung, wenn die Datei bereits eine versionierte URL trägt. Einheitliche Regeln verhindern inkonsistente Antworten und halten die Cache-Hitrate hoch.

Frische vs. Validierung: Direktiven präzise einsetzen

Ich unterscheide klar zwischen Frische (wie lange darf ein Cache eine Kopie ohne Rückfrage nutzen?) und Validierung (wie prüfe ich, ob sie noch gültig ist?). Über Cache-Control steuere ich beides fein granular: max-age legt die Lebensdauer beim Client fest, s-maxage für Shared Caches wie Proxies. public erlaubt Zwischenspeichern in geteilten Caches, private beschränkt es auf den Endbrowser. must-revalidate erzwingt Rückfragen nach Ablauf, während immutable bei versionierten Assets unnötige Revalidierungen verhindert. no-cache verbietet nicht das Caching, sondern verlangt stets eine Revalidierung; no-store hingegen untersagt das Speichern komplett. Ältere Expires-Header nutze ich nur als Fallback, die Logik verlagere ich konsequent auf Cache-Control. Und wenn ich Ausfälle abfedern will, helfen stale-while-revalidate und stale-if-error, um kurzfristig abgelaufene Inhalte weiterzugeben, während ich im Hintergrund aktualisiere oder Fehler überbrücke.

Starke und schwache ETags, Kompression und Varianten

Ich entscheide bewusst zwischen starken und schwachen Validierern. Starke ETags identifizieren Byte-genau dieselbe Repräsentation – ideal, wenn ich auch Range Requests effizient bedienen möchte. Schwache ETags (Präfix W/) reichen, wenn semantische Gleichheit genügt, etwa bei kleinen, irrelevanten Formatänderungen. Wichtig ist der Umgang mit Kompression: Liefere ich sowohl gzip- als auch brotli-kodierte Inhalte, darf ein einziger ETag nicht für alle Varianten gelten. Entweder bilde ich den Tag auf der unkomprimierten Repräsentation und setze zusätzlich einen passenden Vary: Accept-Encoding, oder ich generiere pro Variante konsistente, aber unterschiedliche ETags. So verhindere ich Fehltreffer und 200-Antworten, die eigentlich 304 sein sollten. Bei If-Range kombiniere ich Reichweitenabfragen mit einem Validator: Passt ETag oder Datum, antworte ich mit 206 Partial Content; sonst liefere ich 200 mit vollem Body, damit der Client eine konsistente Basis hat.

Vary-Header und Content-Negotiation sauber beherrschen

Wann immer der Server je nach Anforderung verschiedene Repräsentationen liefert, setze ich Vary korrekt. Typische Kandidaten sind Accept-Encoding (Kompression), Accept-Language (Lokalisierung) oder spezifische Feature-Flags. Ich vermeide es, auf volatile Header wie User-Agent oder gar Cookie zu variieren, weil das die Cache-Hitrate massiv zerschießt. Wo Personalisierung nötig ist, markiere ich Antworten als private oder no-store und trenne sie klar von öffentlich cachebaren Ressourcen. Wichtig: Variationen betreffen auch ETags – jede Variante braucht ihren eigenen, stimmigen Validator. So stelle ich sicher, dass Browser, Proxies und CDNs dieselbe Logik anwenden und keine Variante versehentlich mit einer anderen vermischt wird.

Bedingte Anfragen über GET hinaus

Conditional Requests wirken nicht nur beim Lesen. Für schreibende Methoden nutze ich If-Match oder If-Unmodified-Since, um lost updates zu verhindern. Liefert der Client bei einem PUT oder DELETE den zuletzt gesehenen ETag per If-Match mit, führe ich die Änderung nur aus, wenn der Serverzustand noch identisch ist – sonst antworte ich mit 412 Precondition Failed. Um Clients zu disziplinieren, kann der Server zudem 428 Precondition Required etablieren. Für schnelle Prüfungen ohne Body verwende ich HEAD, das mir dieselben Header wie ein GET liefert; ideal, wenn ich Metadaten testen will. Und bei 304-Antworten gebe ich alle für den Cache relevanten Header erneut mit (Cache-Control, ETag, Expires, Last-Modified), damit der Client seine Metadaten aktualisiert, ohne den Body zu übertragen.

Sicherheit, Datenschutz und Compliance

Personenbezogene oder sensible Inhalte speichere ich nicht im öffentlichen Cache. Hier setze ich Cache-Control: private oder no-store, damit Browser beziehungsweise gar keine Instanz Inhalte persistiert. Vorsicht bei Nutzerkonten und Dashboards: Antworten mit Set-Cookie oder Authorization dürfen nicht versehentlich öffentlich cachebar sein. ETags selbst können als Tracking-Vektor missbraucht werden, wenn sie über lange Zeit stabil bleiben. Dem begegne ich, indem ich Validierer nur dort aktiv einsetze, wo Caching auch gewollt ist, und sie bei nutzerspezifischen Routen deaktiviere oder die Lebensdauer kurz halte. So vereine ich Performance mit Datenschutzanforderungen.

Implementierungsdetails und Performance-Kosten

Die Erzeugung eines ETags darf nicht teurer sein als der Nutzen. Für große Dateien oder teure Renderings speichere ich den Tag zusammen mit Metadaten (Datei-Checksumme, Build-Hash, Datenbank-row version) und rekapituliere ihn nicht bei jedem Request. Bei zusammengesetzten Seiten hilft eine Version-Compose-Strategie: Ich bilde den ETag aus stabilen Teil-ETags (z. B. Template, Datenfragment, Konfiguration), sodass kleine Änderungen einen gezielten, aber reproduzierbaren neuen Wert ergeben. In Clustern synchronisiere ich die Erzeugungslogik in einer gemeinsamen Bibliothek und prüfe sie in CI, damit keine Instanz abweicht. Für extrem große Blobs setze ich auf schnelle Checksummen (CRC64) oder speichere Build-Hashes, statt den Body on-the-fly zu hashen. Wo absolute Byte-Gleichheit nicht nötig ist, genügen schwache ETags als pragmatischer Kompromiss.

Häufige Fehler und wie ich sie vermeide

  • Zufällige ETags: Werden Tags bei jedem Request neu generiert, ist jede Revalidierung wertlos. Ich sorge für deterministische Werte, die nur bei echter Änderung wechseln.
  • Falsche Mischung der Direktiven: no-store mit ETag bringt nichts – der Browser speichert ohnehin nicht. Ich wähle konsistente Kombinationen für das gewünschte Verhalten.
  • Übermäßiges Vary: Variationen auf Cookie oder User-Agent zerlegen den Cache. Ich beschränke Vary auf echte Repräsentationswechsel.
  • Kompressions-Fallen: Ein gemeinsamer ETag für gzip und br führt zu Fehltreffern. Ich verknüpfe ETags sauber mit der konkreten Variante und setze Vary korrekt.
  • Uhrzeit-Drift: Ungenaue Serveruhren verfälschen Last-Modified. Ich halte Zeitquellen synchron, damit If-Modified-Since korrekt greift.
  • Verwechslung von no-cache: Viele lesen „nicht cachen“. Gemeint ist „immer revalidieren“. Für echtes Verbot nutze ich no-store.

Troubleshooting, Metriken und Workflows

Zur Fehlersuche starte ich im Netzwerk-Tab: Stimmt Cache-Control? Kommt bei Revalidierung 304 statt 200? Passen ETag und Last-Modified zwischen Anfrage und Antwort? Ich prüfe Vary, um zu sehen, ob Varianten korrekt erkannt sind. In Logs lasse ich mir Hit/Miss-Quoten, 304-Raten und durchschnittliche Response-Größen pro Pfad ausgeben. Steigt die 304-Rate, sinken Datenvolumen und TTFB typischerweise sichtbar. In Lasttests simuliere ich Wiederholungsaufrufe, um Revalidierungs- statt Transferkosten zu messen. Bei Auffälligkeiten entferne ich schrittweise Störfaktoren: Set-Cookie, überstrenge Vary-Regeln, widersprüchliche Header wie Pragma. So finde ich schnell den Engpass, der die Hitrate drückt.

Service Worker als ergänzende Cache-Schicht

Setze ich einen Service Worker ein, nutze ich ihn als zusätzliche, nicht als widersprechende Ebene. Ich lasse ihn die gleichen Cache-Control-Signale respektieren und kombiniere Strategien wie stale-while-revalidate bewusst mit HTTP-Validierung über ETag und Last-Modified. Für Offline-Fälle kann der Worker kurzfristig veraltene Ressourcen liefern und im Hintergrund revalidieren. Wichtig bleibt, dass er Konditions-Header korrekt weiterreicht, sonst verliere ich die Vorteile von 304 auf der Netzwerkstrecke. So profitieren auch PWA-Szenarien von sauberem HTTP-Caching, statt dessen Mechanismen zu umgehen.

SEO-Effekt und Core Web Vitals

Schnelle Antworten verbessern UX und Nutzersignale, was Rankings begünstigt. Gerade wiederkehrende Besucher profitieren, weil ihr Browser viele Dateien direkt aus dem Cache oder per 304 bestätigt. Diese geringere Latenz wirkt positiv auf FCP, LCP und TTFB, die ich mit gezielter Revalidierung senke. Außerdem spart der Server Rechenzeit, die ich für Lastspitzen oder aufwendige Requests nutzen kann. So bleibe ich performant, während Inhalte korrekt und zeitnah ankommen.

Zusammenfassung: Mein Handlungsfahrplan

Ich setze auf eine klare Kombination aus Cache-Control, Last-Modified und ETag. Für statische Assets wähle ich lange Lebensdauern und sichere mich mit Revalidierung ab, wenn Dateien nicht versioniert sind. Für dynamische Antworten generiere ich belastbare ETags und halte Cluster konsistent. Anschließend prüfe ich mit Tools, Metriken und Logs, ob 304 häufig genug auftreten und passe Einstellungen an. So sichere ich schnelle Auslieferung, geringere Last und ein besseres Nutzererlebnis durch wirksames HTTP Caching.

Aktuelle Artikel