HTTP Conditional Requests reduzieren Übertragungskosten, indem der Client nur dann eine Ressource vollständig lädt, wenn sie seit dem letzten Abruf wirklich geändert wurde. Ich zeige, wie Cache-Validierung mit ETag, Last-Modified, If-None-Match, If-Modified-Since und 304 Not Modified zuverlässig funktioniert und Ladezeiten spürbar sinken.
Zentrale Punkte
- Validierung statt Voll-Download: 304 spart Bandbreite und Zeit.
- ETag und Last-Modified arbeiten zusammen für saubere Kontrolle.
- Cache-Control definiert Frische, Expires ergänzt nur noch.
- Preconditions wie If-Match sichern Schreibvorgänge ab.
- Sicherheit verlangt private/no-store für sensible Inhalte.
Was Conditional Requests im Alltag leisten
Ich setze Conditional Requests ein, um dem Server eine klare Frage zu stellen: Muss ich wirklich neue Daten übertragen oder reicht mein Cache? Der Browser oder ein Proxy sendet Bedingungen mit, der Server prüft, ob sich eine Datei geändert hat, und antwortet bei Gleichheit mit 304 Not Modified ohne Body. Dieses Muster hält HTML, CSS, JavaScript, Bilder und API-Responses aktuell und entlastet die Infrastruktur merklich. Für länger gültige Assets nutze ich lange max-age-Werte und sichere die Aktualität durch Validierung ab. Wer die richtigen Cache-Control-Strategien wählt, holt aus Caches das Maximum heraus, ohne veraltete Inhalte zu riskieren.
Header, die Cache-Validierung ermöglichen
Für verlässliche Cache-Entscheidungen benötigt der Client eindeutige Signale aus der Antwort. Ich setze Cache-Control für Frische und Regeln ein, Expires gelegentlich als Ergänzung, sowie Last-Modified und ETag für die eigentliche Validierung. Last-Modified liefert einen Änderungszeitpunkt, der sich schnell prüfen lässt, während ETag eine Versionskennung bereitstellt, die auch bei dynamischen Inhalten trägt. Wichtig bleibt: no-cache heißt validieren vor Nutzung, nicht löschen. Wer diese Semantik sauber anwendet, erzielt spürbar weniger Datenverkehr bei gleicher Aktualität der Inhalte.
Ablauf eines Conditional-Requests ohne Umwege
Beim ersten Abruf speichert der Client Datei und Validierungswerte wie ETag oder Last-Modified im Cache. Später läuft die Ressource ab oder verlangt vor Nutzung eine erneute Prüfung, und der Client sendet If-None-Match oder If-Modified-Since mit. Der Server vergleicht die Angaben mit dem aktuellen Stand und liefert entweder 200 OK mit neuem Body oder 304 Not Modified ohne Nutzlast. Der Client verwendet dann die vorhandene Kopie weiter und spart Übertragung, TLS-Workload und Zeit. Dieses Ping-Pong wirkt unscheinbar, doch die Wirkung auf Ladegefühl und Serverlast ist deutlich.
ETag vs. Last-Modified im direkten Vergleich
Ich verwende Last-Modified gerne als schnellen, einfachen Hinweis, greife aber für feingranulare Kontrolle zu ETags. Zeitstempel können bei sehr kurzen Änderungsintervallen an Grenzen stoßen oder bei dynamischen Ausgaben verfälschen. ETags bilde ich aus Dateihashes, Datenbankversionen oder Rendervarianten, wodurch jede Inhaltsänderung eine neue Kennung erzeugt. Beide Mechanismen lassen sich kombinieren: Der Client kann If-None-Match und If-Modified-Since parallel senden, und der Server wählt die passende Prüfung. So sichere ich eine zuverlässige Validierung, die bei statischen und dynamischen Ressourcen gleichermaßen greift.
| Kriterium | Last-Modified | ETag |
|---|---|---|
| Genauigkeit | Sekundenauflösung, geeignet für Dateien | Versionskennung für jede Inhaltsänderung |
| Dynamik | Schwach bei häufigen, nicht-dateibasierten Änderungen | Stark für APIs und gerenderte Inhalte |
| Aufwand | Gering, vom Dateisystem lieferbar | Gering bis moderat, Generierung in App/Proxy |
| Konflikte | Uhrenabweichungen möglich | Falsch konfigurierte Weak/Strong-Tags möglich |
Richtige Einstellungen für Browser‑Caching und APIs
Für statische Assets setze ich lange max-age und sichere per ETag oder Dateinamen-Hash ab, damit Updates sofort erkannt werden. HTML und API-Responses markiere ich oft mit no-cache, damit der Client vor Nutzung validiert, ohne jedes Mal alles neu zu laden. Personalisierte Seiten kennzeichne ich mit private, damit Shared Caches nichts Festgehaltenes an andere ausgeben. Fehler entstehen häufig durch widersprüchliche Direktiven oder fehlende Validierungsheader, was ich mit klaren Regeln vermeide. Wer typische Stolpersteine kennt, umgeht sie leicht; gute Beispiele dafür liefert der Beitrag zu Header-Fallen beim Caching, an dem ich mich gerne orientiere.
Statuscodes und Bedingungen wirkungsvoll nutzen
Ich ordne Statuscodes klar zu: 200 OK liefert Inhalte, 304 Not Modified bestätigt die Cache-Nutzung, und 412 Precondition Failed bricht ab, wenn eine Bedingung nicht erfüllt ist. Für sichere Schreiboperationen setze ich If-Match ein, damit Aktualisierungen nur greifen, wenn der ETag der erwarteten Version entspricht. Lesen mit If-None-Match oder If-Modified-Since verhindert überflüssige Nutzlasten und hält Clients synchron. Wichtig ist konsequentes Verhalten: Der gleiche Endpunkt sollte für identische Vorbedingungen auch identisch antworten. Für SEO und Bots beachte ich, wie Caches und Crawler Statusmeldungen interpretieren; ein guter Überblick zu HTTP-Statuscodes und Crawling hilft bei sauberen Entscheidungen.
Sicherheit und Datenschutz beim Caching
Sensible Inhalte behandle ich mit no-store und gebe ihnen damit keine Chance, im Browser- oder Proxy-Cache zu landen. Personalisierte Seiten kennzeichne ich mit Cache-Control: private und prüfe, ob keine personenbezogenen Daten in langfristig gecachten URLs auftauchen. ETags gestalte ich neutral, ohne Rückschlüsse auf Nutzerkonten oder interne IDs zu ermöglichen. Für Login-Ansichten und Banking-Flows deaktiviere ich jeden Zwischenspeicher konsequent. Wer Datenminimierung, geeignete Direktiven und saubere Header kombiniert, reduziert Risiko und wahrt Vertraulichkeit.
Implementierung: Schritte für Server und Anwendung
Am Anfang trenne ich statische von dynamischen Ressourcen und entscheide, wo lange Fristen sinnvoll sind und wo Validierung Vorrang hat. In Nginx, Apache oder einem App-Server konfiguriere ich Cache-Control und aktiviere Last-Modified sowie ETags, wobei ich bei dynamischen Endpunkten die ETag-Erzeugung in die Anwendung lege. Beim Verarbeiten eingehender Requests werte ich If-None-Match und If-Modified-Since aus und antworte mit 304, wenn die Bedingung passt. Für Schreibvorgänge nutze ich If-Match und gebe bei Abweichungen 412 zurück, um Überschreibungen zu verhindern. So entsteht ein konsistentes Verhalten, das Caches effizient nutzt und zugleich Korrektheit sicherstellt.
Metriken, Tests und Monitoring sinnvoll einsetzen
Ich prüfe im Netzwerk-Tab der DevTools, ob Ressourcen aus dem Cache kommen, validiert werden oder frisch geladen sind. Dabei beobachte ich Status, Age-Werte, ETags und die Größe der übertragenen Antwort. Unter Last messe ich Latenz, Transfer-Volumen und Server-CPU, um den tatsächlichen Effekt von 304-Antworten zu sehen. Logs des Reverse Proxys zeigen, ob Shared Caches ihre Aufgabe übernehmen und wie viele Validierungen erfolgreich sind. Mit diesen Daten justiere ich max-age, Cache-Control-Direktiven und ETag-Strategien, bis Reaktionszeiten und Trefferquote stimmen.
Hosting-Performance: Warum Conditional Requests Geld sparen
Jede 304-Antwort spart Bandbreite, reduziert TLS-Overhead und verkürzt die Antwortzeit, was gerade bei vielen gleichartigen Anfragen zählt. In Hosting-Setups mit Reverse Proxies, Load Balancern und CDNs entfalte ich den größten Effekt, wenn ich Shared Caches klar zulasse oder gezielt ausschließe. Weniger Transfer bedeutet häufig auch geringere Traffic-Kosten in Euro und mehr Reserven für echte Lastspitzen. Zudem verbessert sich das Nutzererlebnis, weil wiederholte Seitenaufrufe sowie API-Polls schneller reagieren. So hebe ich Performance-Potenzial, das reine Hardware-Upgrades allein nicht liefern, und nutze bestehende Infrastruktur besser.
Häufige Fehler und pragmatische Lösungen
Widersprüchliche Header wie no-cache gepaart mit weit in der Zukunft liegenden Expires-Daten verwirren Caches; ich setze eindeutige Regeln ohne Doppelungen. Fehlende ETags bei dynamischen Endpunkten führen zu unnötigen Voll-Downloads; ich ergänze eine verlässliche Kennung und werte If-None-Match aus. Zu kurze max-age-Werte verschwenden Potenzial bei selten geänderten Dateien; ich strecke Fristen und sichere trotzdem mit Validierung ab. Aggressives Caching von HTML verzögert sichtbare Änderungen; ich kombiniere no-cache mit ETag und halte Inhalte aktuell. Mit klaren Entscheidungen zu Frische, Validierung und Gültigkeit löse ich diese Stolpersteine und stärke die Planbarkeit.
Vary sauber einsetzen und Repräsentationen steuern
Damit Conditional Requests in Shared Caches sicher funktionieren, achte ich auf eine korrekte Verwendung von Vary. Der Header definiert, welche Anfrage-Header die Repräsentation beeinflussen. Typische Beispiele sind Accept-Encoding (gzip, br), Accept-Language und Accept. Wenn ich Inhalte je Sprache liefere, setze ich Vary: Accept-Language, damit ein Proxy nicht die deutsche Fassung als Antwort auf eine französische Anfrage teilt. Bei personalisierten Inhalten verzichte ich bewusst auf Vary: Cookie und nutze stattdessen Cache-Control: private, um eine unkontrollierbare Explosion der Cache-Keys zu vermeiden. Für Ressourcen, die nur mit gültiger Autorisierung ausgeliefert werden, verwende ich entweder private oder sorge mit Vary: Authorization bzw. Vary auf relevante Header für klare Trennung. Wichtig ist Konsistenz: Der gewählte Satz an Vary-Dimensionen muss stabil bleiben, sonst brechen Cache-Hitrate und Validierungsvorteile ein, weil ständig neue Varianten entstehen.
Strong vs. Weak ETags, Kompression und Byte-Gleichheit
Starke ETags kennzeichnen Byte-für-Byte-identische Repräsentationen und erlauben eine präzise Validierung, auch in Kombination mit Range-Requests. Schwache ETags (W/…) signalisieren nur inhaltliche Gleichheit, nicht zwingend identische Bytes. In der Praxis setze ich starke ETags ein, wenn ich pro Repräsentation (inklusive Kompression) eine eindeutige Kennung erzeugen kann. Sobald ein Reverse Proxy on-the-fly gzip oder brotli anwendet, passe ich die ETag-Generierung an oder wechsle zu schwachen ETags, damit Server und Client nicht fälschlich unterschiedliche Bytes als identisch ansehen. Eine robuste Variante ist, das ETag aus den finalen Bytes der ausgelieferten Antwort zu bilden und zugleich Vary: Accept-Encoding zu setzen. So ist sichergestellt, dass „gzip“ und „br“ jeweils eigene, valide ETags erhalten. Wo ich Byte-Gleichheit nicht sicherstellen kann (z. B. unterschiedliche Zeitstempel im HTML), wähle ich schwache ETags, die trotzdem zuverlässige 304-Antworten erlauben, solange sich die Bedeutung der Seite nicht geändert hat.
Cache-Control im Feintuning: s-maxage, must‑revalidate, stale‑while‑revalidate
Neben max-age nutze ich gezielt feinere Direktiven. s-maxage adressiert ausschließlich Shared Caches (CDNs, Proxies) und erlaubt mir, dort aggressiver zu cachen als im Browser. must-revalidate zwingt Clients, abgelaufene Inhalte nicht heuristisch zu verwenden, sondern stets zu validieren – hilfreich bei kritischen Daten. proxy-revalidate richtet diese Pflicht speziell an Shared Caches. Mit stale-while-revalidate erlaube ich, kurzzeitig eine leicht veraltete Kopie auszuliefern, während im Hintergrund die Validierung läuft; Nutzer sehen sofort etwas, und die nächste Anfrage profitiert von frischen Metadaten. stale-if-error halte ich als Sicherheitsnetz bereit: Fällt der Origin aus oder liefert Fehler, darf der Cache eine definierte Zeit lang die letzte bekannte Kopie liefern. Für Build‑gehashte Assets kombiniere ich lange max-age mit immutable, da sich der Dateiname mit dem Inhalt ändert und Zwischenvalidierungen unnötig sind. Für HTML und APIs bleibt no-cache plus Validator der Goldstandard, um Aktualität sicherzustellen und trotzdem Bandbreite zu sparen.
Weitere Konditionen und Methoden: HEAD, Range und Schreibkonflikte
Conditional Requests beschränken sich nicht auf GET. Ein HEAD-Request liefert nur Header und eignet sich hervorragend für schnelle Validierungen ohne Body. Für große Dateien setze ich Range-Requests ein; mit If-Range stelle ich sicher, dass Teilbereiche nur dann geschickt werden, wenn die Ressource noch zur erwarteten Version passt – andernfalls antworte ich mit 200 und vollständigem Body. Bei schreibenden Operationen sichere ich Konsistenz mit If-Match ab: PUT, PATCH oder DELETE greifen nur, wenn der ETag des Clients zur aktuellen Version passt; sonst antworte ich mit 412 Precondition Failed. Wo ich Vorbedingungen erzwingen will, etwa bei konfliktanfälligen Ressourcen, setze ich 428 Precondition Required ein, um Clients zur Nutzung von If-Match zu bewegen. Zwischen 409 Conflict und 412 wähle ich bewusst: 412 signalisiert verletzte Vorbedingungen, 409 betont inhaltliche Konflikte (z. B. Regeln der Geschäftslogik). Diese Klarheit erleichtert Client-Implementierungen und vermeidet Blindüberschreibungen.
Personalisierung, Cookies und Datenschutz in der Praxis
Bei personalisierten Seiten markiere ich Antworten mit private oder no-store. Ich vermeide, Nutzerzustände in ETags zu codieren, weil solche „per‑User“-ETags unfreiwillig zu Tracking-Markern werden können. Stattdessen trenne ich streng zwischen öffentlichen und privaten Repräsentationen. Cookies im Cache-Key (Vary: Cookie) setze ich nur, wenn absolut nötig, denn sie vergrößern die Variantenvielfalt und senken die Trefferquote dramatisch. Für autorisierte API-Responses halte ich mich an Cache-Control: private und, falls erforderlich, an Vary auf das Authorization-Headerformat. So stelle ich sicher, dass Shared Caches keine vertraulichen Antworten versehentlich teilen. In Anwendungen mit Mischinhalten (öffentliches Grundgerüst, personalisierte Teilbereiche) setze ich auf fragmentiertes Caching oder Edge‑ESI/SSI, während kritische Teile weiter privat bleiben. Das Ergebnis ist Datenschutz ohne Einbruch der Performance.
Betrieb: CDNs, Surrogates und Invalidierungen
In CDN‑Setups kombiniere ich s-maxage mit klaren Validatoren und nutze – falls verfügbar – surrogate-spezifische Direktiven, um Edge‑Caches separat vom Browser zu steuern. Revalidierung reduziert Last am Origin, doch gelegentlich ist eine harte Invalidierung nötig (z. B. Sicherheitsfix). Ich plane dann zwei Wege: Cache-Busting über Dateinamen-Hashes für statische Assets und Purge/Invalidierung für HTML und APIs. Dadurch verhindere ich „Purge‑Stürme“ und halte trotzdem kurze Time‑to‑Freshness. Wichtig ist zudem ein konsistenter Cache‑Key im CDN (inklusive Host, Pfad, Query‑Parametern und relevanten Vary‑Headern). Für Query‑Parameter unterscheide ich bewusst zwischen semantischen (z. B. ?lang=) und rein trackingbezogenen; letztere ignoriere ich im Cache‑Key, um Fragmentierung zu vermeiden. So bleibt die Kette aus Browsercache, Zwischenproxy und CDN transparent und vorhersehbar.
304 im Detail: Header und Metadaten korrekt aktualisieren
Auch eine 304-Antwort trägt wichtige Metadaten. Ich liefere Date, Cache-Control, ETag/Last-Modified und – falls relevant – Expires und Vary erneut aus, damit der Client seine Cache-Einträge aktualisieren kann. Content-Type und Content-Encoding müssen nicht zwingend wiederholt werden, können aber zur Klarheit beitragen. Wichtig ist, dass 304 keine Body-Nutzlast enthält und dass ich konsistente Validatoren versende: Ein Wechsel des ETag in der 304 ohne Änderung der Repräsentation führt zu Verwirrung. Werden Richtlinien angepasst (z. B. längeres max-age), kann der Client die Metadaten übernehmen, ohne den Body neu laden zu müssen – ein kleiner Hebel mit großer Wirkung auf die wahrgenommene Performance.
Test- und Debug-Strategien für saubere Validierung
In den DevTools kontrolliere ich gezielt folgende Punkte: from disk cache vs. from memory cache vs. revalidated; Status 200/304; Age‑Header in Antworten aus Shared Caches; ETag/Last‑Modified‑Konsistenz sowie die Wirkung von Vary. Für reproduzierbare Tests deaktiviere ich temporär den Browsercache oder nutze einen privaten Modus. Auf Serverseite werte ich Logs zum Verhältnis 200/304, zur durchschnittlichen Response‑Größe und zur CPU-Auslastung aus. Warnsignale sind z. B. viele 200‑Antworten auf Ressourcen mit kurzen Änderungsintervallen (fehlende Validatoren), revalidation loops (abweichende Zeiten/Uhrendrift) oder ungewöhnlich viele Varianten pro URL (übermäßiges Vary). Mit Lasttests prüfe ich, wie sich s-maxage, stale‑while‑revalidate und If‑None‑Match unter Druck verhalten – und justiere Direktiven, bis Durchsatz und Latenz zusammenpassen.
Edge Cases und robuste Defaults
Nicht jede Ressource hat klare Änderungsregeln. Für generierte Sitemaps, Feeds oder Dashboards setze ich vorsichtige Defaults: no-cache plus ETag/Last‑Modified. Bei instabilen Uhren zwischen Upstream‑Systemen vermeide ich starre Sekundenvergleiche und bevorzuge ETags, die unabhängig von Zeitstempeln sind. Wenn Kompression variabel ist (unterschiedliche gzip‑Level), binde ich das Ergebnis der Kompression in die ETag‑Erzeugung ein oder wechsle zu schwachen ETags. Und wenn Clients ohne Validatoren anfragen, gebe ich eindeutige Signale mit: Entweder klare Frische (max-age/immutable) oder klare Validierung (no-cache + ETag). Diese robusten Defaults sorgen dafür, dass auch unvollständige oder fehlerhafte Clients keine ungewollten Lastspitzen auslösen.
Kurze Zusammenfassung
Conditional Requests verbinden Tempo und Aktualität, indem Clients nur dann vollständige Ressourcen abrufen, wenn sich Inhalte geändert haben. Ich nutze Cache-Control für Frische, kombiniere Last-Modified und ETag für verlässliche Prüfungen und antworte konsequent mit 304, sofern Bedingungen erfüllt sind. Personalisierte und vertrauliche Inhalte isoliere ich mit private oder no-store, damit keine Daten in Shared Caches landen. Messungen in DevTools, Logs und Metriken zeigen mir, wo ich Fristen strecken oder Validierungslogik schärfen kann. Wer diese Bausteine zusammendenkt, erzielt schnellere Seiten, entlastete Server und eine rundere User Experience ohne Datenverschwendung.


