Backend Development 10 min read

Understanding HTTP Caching: Types, Headers, and ETag Mechanisms

This article explains why caching is essential for web performance, describes the two main HTTP cache types (strong and negotiated), compares Expires and Cache‑Control headers, and details ETag generation, validation, and best‑practice considerations in server implementations such as Nginx.

ByteFE
ByteFE
ByteFE
Understanding HTTP Caching: Types, Headers, and ETag Mechanisms

1. Why Caching Is Needed

Reuse previously fetched resources in appropriate scenarios.

Significantly improve website performance and response speed.

Reduce network traffic and rendering wait time.

Lower server load.

2. HTTP Cache Types

Strong cache

Negotiated cache

3. Strong Cache

For strong caching, the server sets a fixed expiration time in the response headers of static resources; within this period the cached copy is used directly, otherwise the negotiated cache strategy is applied.

3.1 Expires

The Expires header contains the absolute expiration time of a strongly cached resource.

A value of 0 means the resource is expired or not strongly cached.

3.2 Cache-Control

The generic header uses directives to control caching. Common directives include:

no-cache : forces the cache to revalidate with the origin server before using the cached copy.

When no-cache is present, the cache must submit the request to the server for validation (negotiated cache).

no-store : instructs the cache not to store any part of the request or response.

3.3 Difference Between Expires and Cache-Control

Time type: Expires uses an absolute timestamp; Cache-Control uses a relative duration (e.g., max-age=60 ).

Priority: Cache-Control overrides Expires when both are present.

HTTP version: Expires originates from HTTP/1.0 and enjoys broader compatibility; Cache-Control is from HTTP/1.1 and may be unsupported by very old browsers, which then fall back to Expires .

4. Negotiated Cache

Negotiated cache consults the server to decide whether to use the cached copy, typically resulting in a 304 Not Modified response.

The following headers control negotiated caching rather than strong caching:

4.1 Pragma

Pragma is an HTTP/1.0 general header; if Cache-Control is absent, Pragma: no-cache behaves like Cache-Control: no-cache , forcing revalidation.

The only valid value is no-cache , and its priority is higher than Cache-Control .

4.2 Cache-Control

The same directives used for strong caching can also drive negotiated caching.

Cache-Control: no-cache and Cache-Control: max-age=0 have identical effect: they force the client to send a request to the server for validation.

5. Negotiated Strategies

When Pragma or Cache-Control: no-cache appears, a negotiated strategy is required. Common header pairs are:

ETag / If-None-Match

Last-Modified / If-Modified-Since

Advantages and Disadvantages

If a file is changed and then reverted, its modification time changes even though the content is identical, causing unnecessary cache invalidation.

ETag uses a content hash, so unchanged content keeps the same cache entry, reducing bandwidth.

Therefore, ETag is generally more precise and efficient than Last-Modified .

6. ETag

6.1 What Is an ETag?

An ETag (Entity Tag) is a summary identifier for a requested resource, sent in the HTTP response header, e.g., ETag: W/"50b1c1d4f775c61:df3" .

6.2 ETag Formats

ETag: W/"xxxxxxxx" (weak validator)

ETag: "xxxxxxx" (strong validator)

Strong Validation

All bytes of the resource must match exactly.

Weak Validation (W/ prefix)

Allows minor differences (e.g., dynamic footers or ads) while still treating the resource as the same.

6.3 Conditions for Generating an ETag

The ETag must change when the file changes.

Computation should be lightweight and not overly CPU‑intensive.

If using a hash algorithm (MD5, SHA‑1, SHA‑256), consider its CPU cost.

No single “best” algorithm; choose one suited to the scenario.

ETag generation must be consistent across distributed servers.

6.4 How Nginx Generates an ETag

In Nginx source code, the ETag is built from last_modified and content_length :

etag->value.len = ngx_sprintf(etag->value.data, "\"%xT-%xO\"",
                     r->headers_out.last_modified_time,
                     r->headers_out.content_length_n)
                     - etag->value.data;

Translated to pseudocode:

etag = header.last_modified + "-" + header.content_length

Thus, Nginx’s ETag combines the last‑modified timestamp and the file size, making it a more precise version of Last-Modified .

6.5 How Last-Modified Is Generated

On Linux, the file’s mtime (modification time) is used as the Last-Modified header, while ctime reflects attribute changes. HTTP servers typically choose mtime to ensure cross‑platform compatibility.

r->headers_out.status = NGX_HTTP_OK;
r->headers_out.content_length_n = of.size;
r->headers_out.last_modified_time = of.mtime;

6.6 Does a Changed ETag Guarantee Changed Content?

Not necessarily; a file could change and revert within a second without altering size, causing a different ETag despite identical content.

Such edge cases are rare, so a reasonably efficient algorithm is acceptable in practice.

References

MDN Web Docs – Cache‑Control header

Nginx source: ngx_http_core_module.c

Nginx source: ngx_http_static_module.c

BackendCachingweb performanceHTTPcache controlETag
ByteFE
Written by

ByteFE

Cutting‑edge tech, article sharing, and practical insights from the ByteDance frontend team.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.