HTTP Conditional Requests reduce transmission costs by ensuring that the client only loads a resource completely if it has actually been changed since the last request. I will show how Cache validation with ETag, Last-Modified, If-None-Match, If-Modified-Since and 304 Not Modified works reliably and loading times are noticeably reduced.
Key points
- Validation instead of full download: 304 saves bandwidth and time.
- ETag and Last-Modified work together for clean control.
- Cache control defines freshness, expires only supplements.
- Preconditions such as If-Match save write operations.
- Security requires private/no-store for sensitive content.
What conditional requests do in everyday life
I set Conditional requests to ask the server a clear question: Do I really need to transfer new data or is my cache sufficient? The browser or a proxy sends conditions, the server checks whether a file has changed and responds with 304 Not Modified without body. This pattern keeps HTML, CSS, JavaScript, images and API responses up to date and significantly reduces the load on the infrastructure. For longer valid assets, I use long max-age values and ensure that they are up to date through validation. If you have the right Cache control strategies gets the most out of caches without risking outdated content.
Headers that enable cache validation
For reliable Cache-The client needs clear signals from the response to make decisions. I use Cache-Control for freshness and rules, Expires occasionally as a supplement, and Last-Modified and ETag for the actual validation. Last-Modified provides a modification time that can be checked quickly, while ETag provides a version identifier that is also valid for dynamic content. Important: no-cache means validate before use, not delete. If you apply these semantics correctly, you will achieve noticeably less data traffic with the same up-to-dateness of the Contents.
Sequence of a conditional request without detours
On the first call, the client saves the file and validation values such as ETag or Last-Modified in the cache. Later, the resource expires or requires a new check before use, and the client sends If-None-Match or If-Modified-Since. The server compares the information with the current status and returns either 200 OK with a new body or 304 Not Modified without payload. The client then continues to use the existing copy and saves transmission, TLS workload and time. This ping-pong seems inconspicuous, but the Effect on loading feel and server load is clear.
ETag vs. Last-Modified in direct comparison
I use Last-Modified I like to use timestamps as a quick, simple reference, but use ETags for fine-grained control. Timestamps can reach their limits with very short change intervals or falsify dynamic outputs. I create ETags from file hashes, database versions or render variants, whereby each content change generates a new identifier. Both mechanisms can be combined: The client can send If-None-Match and If-Modified-Since in parallel, and the server selects the appropriate check. How to ensure a reliable Validation, which applies equally to static and dynamic resources.
| Criterion | Last-Modified | ETag |
|---|---|---|
| Accuracy | Second resolution, suitable for files | Version identifier for each content change |
| Dynamics | Weak with frequent, non-file-based changes | Strong for APIs and rendered content |
| Expenditure | Low, available from the file system | Low to moderate, generation in app/proxy |
| Conflicts | Clock deviations possible | Incorrectly configured weak/strong tags possible |
Correct settings for browser caching and APIs
For static assets, I use long max-age and save via ETag or file name hash so that updates are recognized immediately. I often mark HTML and API responses with no-cache so that the client validates before use without having to reload everything every time. I mark personalized pages with private so that shared caches do not output anything that has been retained to others. Errors are often caused by contradictory directives or missing validation headers, which I avoid with clear rules. If you know the typical stumbling blocks, you can easily avoid them; the article on Header traps in caching, which I like to follow.
Using status codes and conditions effectively
I arrange Status codes clearly: 200 OK delivers content, 304 Not Modified confirms cache usage, and 412 Precondition Failed aborts if a condition is not met. For secure write operations, I use If-Match so that updates only take effect if the ETag corresponds to the expected version. Reading with If-None-Match or If-Modified-Since prevents superfluous payloads and keeps clients synchronized. Consistent behavior is important: The same endpoint should respond identically for identical preconditions. For SEO and bots, I pay attention to how caches and crawlers interpret status messages; a good overview of HTTP status codes and crawling helps with clean decisions.
Security and data protection for caching
I treat sensitive content with no-store and thus give them no chance of ending up in the browser or proxy cache. I mark personalized pages with Cache-Control: private and check that no personal data appears in long-term cached URLs. I design ETags neutrally, without allowing conclusions to be drawn about user accounts or internal IDs. I consistently deactivate all caching for login views and banking flows. If you combine data minimization, suitable directives and clean headers, you reduce risk and preserve Confidentiality.
Implementation: Steps for server and application
At the beginning I separate static of dynamic resources and decide where long deadlines make sense and where validation has priority. In Nginx, Apache or an app server, I configure cache control and activate last-modified and ETags, whereby I place the ETag generation in the application for dynamic endpoints. When processing incoming requests, I evaluate If-None-Match and If-Modified-Since and respond with 304 if the condition matches. For write operations, I use If-Match and return 412 in the event of deviations to prevent overwriting. This creates a consistent behavior that uses caches efficiently and at the same time Correctness ensures.
Use metrics, tests and monitoring sensibly
I check the Network-tab of the DevTools to check whether resources are coming from the cache, are being validated or are freshly loaded. I monitor the status, age values, ETags and the size of the transferred response. Under load, I measure latency, transfer volume and server CPU to see the actual effect of 304 responses. Logs from the reverse proxy show whether shared caches are doing their job and how many validations are successful. I use this data to adjust max-age, cache control directives and ETag strategies until response times and Hit rate vote.
Hosting performance: Why conditional requests save money
Any 304-The shared cache response saves bandwidth, reduces TLS overhead and shortens the response time, which is particularly important for many similar requests. In hosting setups with reverse proxies, load balancers and CDNs, I achieve the greatest effect when I clearly allow or specifically exclude shared caches. Less transfer often also means lower traffic costs in euros and more reserves for real load peaks. The user experience is also improved because repeated page views and API polls respond more quickly. In this way, I leverage performance potential that pure hardware upgrades alone cannot deliver and utilize existing Infrastructure better.
Common mistakes and pragmatic solutions
Contradictory Header like no-cache paired with expires far in the future confuse caches; I set clear rules without duplication. Missing ETags for dynamic endpoints lead to unnecessary full downloads; I add a reliable identifier and evaluate if-none-match. Too short max-age values waste potential with rarely changed files; I stretch deadlines and still secure them with validation. Aggressive caching of HTML delays visible changes; I combine no-cache with ETag and keep content up to date. With clear decisions on freshness, validation and validity, I solve these stumbling blocks and strengthen the Plannability.
Use Vary cleanly and control representations
To ensure that conditional requests work safely in shared caches, I make sure to use the correct Vary. The header defines which request headers influence the representation. Typical examples are Accept-Encoding (gzip, br), Accept-Language and Accept. If I deliver content per language, I set Vary: Accept-Language so that a proxy does not share the German version in response to a French request. For personalized content, I deliberately do not use Vary: Cookie and instead use Cache control: private, to avoid an uncontrollable explosion of cache keys. For resources that are only delivered with valid authorization, I either use private or ensure clear separation with Vary: Authorization or Vary on relevant headers. Consistency is important: the selected set of Vary dimensions must remain stable, otherwise the cache hit rate and validation benefits will collapse because new variants are constantly being created.
Strong vs. weak ETags, compression and byte equality
Strong ETags identify byte-by-byte identical representations and allow precise validation, also in combination with range requests. Weak ETags (W/...) only signalize content equality, not necessarily identical bytes. In practice, I use strong ETags if I can generate a unique identifier for each representation (including compression). As soon as a reverse proxy uses gzip or brotli on-the-fly, I adapt the ETag generation or switch to weak ETags so that the server and client do not mistakenly regard different bytes as identical. A robust variant is to form the ETag from the final bytes of the delivered response and at the same time to use Vary: Accept-Encoding to be set. This ensures that „gzip“ and „br“ each receive their own valid ETags. Where I cannot ensure byte equality (e.g. different timestamps in HTML), I choose weak ETags that still allow reliable 304 responses as long as the meaning of the page has not changed.
Cache control in fine-tuning: s-maxage, must-revalidate, stale-while-revalidate
Besides max-age I specifically use finer directives. s-maxage addresses exclusively Shared caches (CDNs, proxies) and allows me to cache more aggressively there than in the browser. must revalidate forces clients not to use expired content heuristically, but to always validate it - helpful for critical data. proxy revalidate addresses this obligation specifically to shared caches. With stale-while-revalidate I allow a slightly outdated copy to be delivered temporarily while validation is running in the background; users see something immediately, and the next request benefits from fresh metadata. stale-if-error as a safety net: If the origin fails or returns errors, the cache is allowed to deliver the last known copy for a defined time. For build-hashed assets, I combine long max-age with immutable, as the file name changes with the content and intermediate validations are unnecessary. For HTML and APIs, no-cache plus validator remains the gold standard to ensure up-to-dateness and still save bandwidth.
Further conditions and methods: HEAD, range and writing conflicts
Conditional requests are not limited to GET. A HEAD-request only provides headers and is ideal for quick validations without a body. For large files I use range-requests; with If-Range I make sure that partial areas are only sent if the resource still matches the expected version - otherwise I respond with 200 and a complete body. For write operations, I ensure consistency with If-Match ab: PUT, PATCH or DELETE only work if the ETag of the client matches the current version; otherwise I respond with 412 Precondition Failed. Where I want to enforce preconditions, for example with conflict-prone resources, I use 428 Precondition Required to get clients to use If-Match. I deliberately choose between 409 Conflict and 412: 412 signals violated preconditions, 409 emphasizes content conflicts (e.g. business logic rules). This clarity facilitates client implementations and avoids blind overrides.
Personalization, cookies and data protection in practice
For personalized pages, I mark answers with private or no-store. I avoid encoding user states in e-tags because such „per-user“ e-tags can involuntarily become tracking markers. Instead, I make a strict distinction between public and private representations. I only set cookies in the cache key (Vary: Cookie) if absolutely necessary, because they increase the number of variants and dramatically reduce the hit rate. For authorized API responses I stick to Cache control: private and, if necessary, to Vary on the Authorization header format. This is how I ensure that shared caches do not inadvertently share confidential responses. In applications with mixed content (public basic framework, personalized sub-areas), I rely on fragmented caching or edge ESI/SSI, while critical parts remain private. The result is data protection without a drop in performance.
Operation: CDNs, surrogates and invalidations
In CDN setups I combine s-maxage with clear validators and - if available - use surrogate-specific directives to control edge caches separately from the browser. Revalidation reduces the load on Origin, but occasionally a hard invalidation is necessary (e.g. security fix). I then plan two ways: Cache busting via file name hashes for static assets and Purge/Invalidation for HTML and APIs. This prevents „purge storms“ and still maintains a short time-to-freshness. It is also important to have a consistent cache key in the CDN (including host, path, query parameters and relevant Vary headers). For query parameters, I consciously differentiate between semantic (e.g. ?lang=) and purely tracking-related parameters; I ignore the latter in the cache key to avoid fragmentation. This keeps the chain of browser cache, intermediate proxy and CDN transparent and predictable.
304 in detail: Update header and metadata correctly
Also a 304-response carries important metadata. I deliver Date, Cache-Control, ETag/Last-Modified and - if relevant - Expires and Vary again so that the client can update its cache entries. Content-Type and Content-Encoding do not necessarily have to be repeated, but can contribute to clarity. It is important that 304 does not contain a body payload and that I send consistent validators: Changing the ETag in the 304 without changing the representation leads to confusion. If guidelines are adjusted (e.g. longer max-age), the client can adopt the metadata without having to reload the body - a small lever with a big impact on perceived performance.
Test and debug strategies for clean validation
I specifically check the following points in DevTools: from disk cache vs. from memory cache vs. revalidated; Status 200/304; Age header in responses from shared caches; ETag/Last-Modified consistency and the effect of Vary. For reproducible tests, I temporarily deactivate the browser cache or use a private mode. On the server side, I evaluate logs on the 200/304 ratio, the average response size and CPU utilization. Warning signals are, for example, many 200 responses to resources with short change intervals (missing validators), revalidation loops (deviating times/clock drift) or an unusually large number of variants per URL (excessive Vary). I use load tests to check how s-maxage, stale-while-revalidate and if-none-match behave under pressure - and adjust directives until throughput and latency match.
Edge cases and robust defaults
Not every resource has clear change rules. I set careful defaults for generated sitemaps, feeds or dashboards: no-cache plus ETag/Last-Modified. With unstable clocks between upstream systems, I avoid rigid second comparisons and prefer ETags that are independent of timestamps. If compression is variable (different gzip levels), I include the result of the compression in the ETag generation or switch to weak ETags. And if clients request without validators, I send clear signals: Either clear freshness (max-age/immutable) or clear validation (no-cache + ETag). These robust defaults ensure that even incomplete or faulty clients do not trigger any unwanted load peaks.
Brief summary
Connect conditional requests Speed and timeliness by having clients only retrieve complete resources when content has changed. I use cache control for freshness, combine last-modified and ETag for reliable checks and consistently respond with 304 if conditions are met. I isolate personalized and confidential content with private or no-store so that no data ends up in shared caches. Measurements in DevTools, logs and metrics show me where I can stretch deadlines or sharpen validation logic. If you think about these building blocks together, you will achieve faster pages, reduced server load and a rounder User experience without wasting data.


