Tidak suka iklan? Pergi Bebas Iklan Hari ini

HTTP Cache Headers Cache-Control, ETag, and max-age Without Guessing

Diperbarui pada

A practical guide to HTTP cache headers for web developers: what Cache-Control directives actually do, how ETags trigger 304 revalidation, how to pick TTLs that survive deployments, and the mistakes that make caching hurt instead of help.

HTTP Cache Headers: Cache-Control, ETag, and max-age Without Guessing 1
IKLAN · HAPUS?

Every HTTP response you send is either cached or it isn’t — and if you’re not deliberate about it, the browser decides for you. The result is usually a mix of aggressive caching for things that change frequently and no caching for things that rarely change. Both hurt your users.

This guide gives you the mental model to set HTTP cache headers correctly the first time: what each directive controls, how ETags trigger revalidation, and how to pick TTLs that survive deployments without serving stale content.

How browser caching actually works

When a browser requests a resource, it first checks its local cache. If a fresh cached copy exists, it serves it immediately — no network request at all. If the cached copy might be stale, it sends a conditional request to the origin. The origin either confirms the resource hasn’t changed (304 Not Modified) or sends the full updated response (200 OK).

CDNs sit in the middle of this lifecycle. They cache responses closer to users geographically, and they obey the same HTTP cache headers — with a few CDN-specific extensions like s-maxage.

Three questions determine caching behavior:

  • Is this response cacheable at all? Controlled by Cache-Control: no-store atau private
  • How long is it fresh? Controlled by max-age atau s-maxage
  • How is staleness validated? Controlled by ETags or Last-Modified

Cache-Control header directives

Itu Cache-Control header is the primary way to declare caching policy. Multiple directives are comma-separated. Here’s what each one actually does:

max-age

max-age=N tells caches (browser and CDN) how many seconds the response stays fresh. A response with max-age=86400 is considered fresh for exactly 24 hours from when it was received. After that, the cache must revalidate before serving it again.

For static assets with versioned filenames (like main.abc123.js), a full year is common: max-age=31536000. For HTML documents, a much shorter window — or no caching at all — is safer, since HTML references those versioned assets.

s-maxage

s-maxage overrides max-age for shared caches (CDNs, proxy servers) only. The browser ignores it. This lets you serve users long-cached responses while keeping the CDN edge fresher. A typical pattern is Cache-Control: public, max-age=3600, s-maxage=86400 — browser caches for 1 hour, CDN caches for 24 hours.

no-cache

no-cache doesn’t mean “don’t cache.” It means the cache must revalidate with the origin before serving the stored response, even if it’s still fresh. The response is cached, but every use requires a round-trip to check it’s still valid. This is appropriate for content that changes frequently but benefits from the bandwidth savings of a 304 response.

no-store

no-store is the only directive that actually prevents caching. No browser cache, no CDN cache, no disk write. Use it for responses containing sensitive user data — bank statements, medical records, tokens. Don’t use it as a default because you haven’t thought about caching yet.

public and private

public explicitly permits shared caches (CDNs) to cache the response, even when the request had an Authorization header. private restricts caching to the end user’s browser only — CDNs must not cache it. For authenticated responses that are user-specific, private prevents one user’s response from being served to another.

must-revalidate

must-revalidate prevents caches from serving stale responses when they can’t reach the origin. Without it, a cache might serve an expired response if the network is unavailable. With it, the request fails with a 504 if the origin is unreachable. Use it for content where serving stale data is worse than an error.

ETags: precision revalidation

An ETag is a server-generated identifier for a specific version of a resource — think of it as a fingerprint for the response body. The server sends it in the response:

ETag: "abc123def456"

When the cached copy expires, the browser sends a conditional GET with the stored ETag:

If-None-Match: "abc123def456"

If the resource hasn’t changed, the server responds with 304 Not Modified and an empty body — saving bandwidth while still confirming freshness. If it has changed, the server responds with 200 OK and the new ETag.

Strong vs weak ETags

A strong ETag ("abc123") means the response is byte-for-byte identical. A weak ETag (W/"abc123") means the responses are semantically equivalent but may differ in trivial ways like whitespace or header order. Weak ETags can be generated more efficiently but can’t be used in range requests. Unless you have a specific reason for weak ETags, use strong ones.

Last-Modified: the older alternative

Before ETags, servers used Last-Modified timestamps for revalidation. The server sends:

Last-Modified: Thu, 01 May 2026 12:00:00 GMT

The browser revalidates with:

If-Modified-Since: Thu, 01 May 2026 12:00:00 GMT

The server returns 304 if the resource hasn’t changed since that timestamp.

The weakness: timestamps have one-second granularity. A file modified twice within the same second will appear unchanged. ETags handle this correctly and are preferred. Most modern frameworks send both — the browser uses whichever is available, with ETags taking priority.

Cache-busting without breaking deployments

A long max-age on a static asset is only safe if the URL changes when the content changes. Two strategies exist:

URL fingerprinting (recommended)

Include a hash of the file content in the filename: main.a1b2c3d4.js. When the file changes, the hash changes, the URL changes, and the browser fetches the new file — bypassing the cache entirely. The old URL remains in cache but is never requested again once HTML references the new URL.

Webpack, Vite, and most modern bundlers do this automatically. The pattern lets you safely set Cache-Control: public, max-age=31536000, immutable — the immutable directive tells the browser not to bother revalidating even if the cache entry is technically stale.

Query strings (less reliable)

Appending a version to the URL as a query string (main.js?v=1.2.3) technically creates a different URL, but some CDNs and proxies ignore query strings when building cache keys. URL fingerprinting in the path is more reliable and universally supported.

Common mistakes that hurt caching

Caching API responses that shouldn’t be cached

JSON APIs that return user-specific or time-sensitive data should use Cache-Control: no-store or at minimum private, no-cache. A common mistake is letting a CDN cache a response like /api/user/profile and serving one user’s data to another. If your API doesn’t set a Cache-Control header, some CDNs will cache it anyway using heuristics.

Forgetting Vary: Accept-Encoding

If your server serves both compressed and uncompressed versions of a resource depending on the client’s Accept-Encoding header, the cache must store separate copies for each variant. Without Vary: Accept-Encoding, a CDN might cache the gzip version and serve it to a client that doesn’t support gzip — or vice versa. Always set it when compression is conditional.

Using no-cache when you mean no-store

Developers often write Cache-Control: no-cache when they want to prevent caching entirely, but no-cache still stores the response — it just requires revalidation before each use. Use no-store when you genuinely don’t want the response persisted anywhere.

Setting max-age on HTML without a cache-busting strategy

HTML documents reference versioned assets. If you cache your HTML with a long max-age, users won’t pick up the new asset filenames after a deployment — they’ll keep running the old HTML, which references the old hashes. Set a short TTL (or no-cache) on HTML, and reserve long TTLs for the immutable assets HTML references.

Calculate your expiry window before you ship

Picking a max-age value is easier when you can visualize what it means in wall-clock time. The Kalkulator TTL Cache HTTP / max-age on iotools.cloud lets you enter a TTL in seconds and see the exact expiry datetime. Useful for sanity-checking values like 86400 (24 hours), 2592000 (30 days), or 31536000 (1 year) before committing them to your server config or CDN rules.

A practical cache policy checklist

  • HTML documents: Cache-Control: no-cache — always revalidate, benefit from 304s when nothing changed
  • Versioned static assets (JS, CSS, images with hash in filename): Cache-Control: public, max-age=31536000, immutable
  • Unversioned static assets (fonts, favicon): Cache-Control: public, max-age=604800 (1 week)
  • API responses (public, time-sensitive): Cache-Control: public, max-age=60, s-maxage=300 — short browser TTL, longer CDN TTL
  • API responses (user-specific): Cache-Control: private, no-cache
  • Sensitive data: Cache-Control: no-store
  • Always set Vary: Accept-Encoding when serving compressed responses conditionally

ETags should be on by default for anything you’re caching — they’re the mechanism that makes revalidation bandwidth-efficient. Most web servers (Nginx, Apache, Caddy) generate ETags automatically unless you’ve disabled them.

Ingin bebas iklan? Bebas Iklan Hari Ini

Instal Ekstensi Kami

Tambahkan alat IO ke browser favorit Anda untuk akses instan dan pencarian lebih cepat

Ke Ekstensi Chrome Ke Ekstensi Tepi Ke Ekstensi Firefox Ke Ekstensi Opera

Papan Skor Telah Tiba!

Papan Skor adalah cara yang menyenangkan untuk melacak permainan Anda, semua data disimpan di browser Anda. Lebih banyak fitur akan segera hadir!

IKLAN · HAPUS?
IKLAN · HAPUS?
IKLAN · HAPUS?

Pojok Berita dengan Sorotan Teknologi

Terlibat

Bantu kami untuk terus menyediakan alat gratis yang berharga

Belikan aku kopi
IKLAN · HAPUS?