Skip to content
← All articles

HTTP Caching Guide: Cache-Control, ETag, Expires

Why HTTP Caching Matters

HTTP caching is one of the most effective ways to speed up website loading. When a browser stores a local copy of a resource, subsequent requests are served instantly without contacting the server. This reduces server load, saves bandwidth, and dramatically improves user experience.

According to Google, reducing load time by 100ms increases conversions by 1%. Properly configured caching can reduce repeat visit load times by 80-90%.

Core Caching Headers

Cache-Control

The Cache-Control header is the primary caching mechanism in HTTP/1.1 and beyond. It supersedes the deprecated Pragma header and takes priority over Expires.

Key directives:

  • public — resource can be cached by any intermediary (CDN, proxy)
  • private — cache only in the user's browser (for personalized content)
  • max-age=N — cache lifetime in seconds
  • s-maxage=N — lifetime for shared caches (CDN), overrides max-age
  • no-cache — may cache, but must revalidate with server before use
  • no-store — completely prohibit caching (for sensitive data)
  • must-revalidate — must check with server after TTL expires
  • immutable — resource will never change (for hashed filenames)

Example for static assets with content hash:

Cache-Control: public, max-age=31536000, immutable

Example for HTML pages:

Cache-Control: no-cache

Example for API документацию with private data:

Cache-Control: private, no-store

ETag (Entity Tag)

An ETag is a unique identifier for a specific version of a resource. The server generates a hash of the content and sends it in the response header:

ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"

On subsequent requests, the browser sends If-None-Match with the stored ETag. If the resource hasn't changed, the server responds with 304 Not Modified without a body, saving bandwidth.

Two types of ETags exist:

  • Strong ETag — byte-for-byte identical: "abc123"
  • Weak ETag — semantically equivalent: W/"abc123"

Last-Modified and If-Modified-Since

The Last-Modified header contains the date when the resource was last changed. It's a simpler validation mechanism than ETag but less precise — accuracy is limited to one second.

Last-Modified: Wed, 21 Oct 2025 07:28:00 GMT

On subsequent requests, the browser sends If-Modified-Since. ETag takes priority over Last-Modified when both are present.

Expires

A legacy HTTP/1.0 header that sets an absolute expiration date:

Expires: Thu, 01 Dec 2026 16:00:00 GMT

Problems with Expires: depends on client-server clock synchronization, less flexible than Cache-Control. Use Cache-Control: max-age instead.

Caching Strategies by Resource Type

HTML Documents

HTML pages typically reference CSS and JS files, so they should always check for freshness:

Cache-Control: no-cache
ETag: "page-v42"

This allows the browser to cache the page but check for updates on every visit.

CSS, JavaScript, Fonts

Static resources with a hash in the filename (e.g., app.a1b2c3.js) can be cached indefinitely:

Cache-Control: public, max-age=31536000, immutable

Without a filename hash, use a shorter TTL:

Cache-Control: public, max-age=86400

Images

Images rarely change. For optimized images with unique URLs:

Cache-Control: public, max-age=2592000

That's 30 days — a good balance between caching and the ability to update.

API Responses

For APIs with frequently changing data:

Cache-Control: private, no-cache, max-age=0

For rarely updated public APIs:

Cache-Control: public, max-age=300, s-maxage=600

Common Mistakes

  • no-cache does NOT mean don't cache. The no-cache directive allows caching but requires revalidation. For a complete ban, use no-store.
  • Same TTL for all resources. HTML and static assets need different strategies.
  • No versioning. Without a hash or version in the URL, users will see stale CSS/JS after updates.
  • Ignoring CDN. Without s-maxage, CDN will use max-age, which may not be desired.
  • Caching personal data as public. Responses with cookies or personalization should be private.

How to Verify Your Caching Setup

Check your website's caching headers using the Enterno.io HTTP Checker. The tool displays all response headers including настройка Cache-Control, ETag, Expires, and Last-Modified, helping you verify everything is configured correctly.

In Chrome DevTools, the Network tab's Size column shows (disk cache) or (memory cache) for cached resources, and a 304 status code for successfully revalidated ones.

# HTML — always revalidate
location ~* \.html$ {
    add_header Cache-Control "no-cache";
}

# Hashed static assets — cache forever
location ~* \.(js|css)$ {
    add_header Cache-Control "public, max-age=31536000, immutable";
}

# Images — cache for 30 days
location ~* \.(jpg|jpeg|png|gif|webp|svg|ico)$ {
    add_header Cache-Control "public, max-age=2592000";
}

# Fonts — cache for 1 year
location ~* \.(woff2?|ttf|eot)$ {
    add_header Cache-Control "public, max-age=31536000, immutable";
    add_header Access-Control-Allow-Origin "*";
}

Summary

Proper HTTP caching is a balance between speed and content freshness. Use Cache-Control as your primary tool, ETag for validation, and file versioning for safe long-term caching. Regularly check your resource headers to ensure everything is configured correctly.

Check your website right now

Check your site's HTTP status →
More articles: HTTP
HTTP
HTTP 301 vs 302 Redirect: Differences and When to Use Each
15.04.2026 · 122 views
HTTP
The Complete HTTP Request Lifecycle: From URL to Rendered Page
16.03.2026 · 323 views
HTTP
X-Forwarded-For Header: Understanding Client IP Behind Proxies
16.03.2026 · 178 views
HTTP
HTTP 500 Internal Server Error: What It Means and How to Fix
15.04.2026 · 118 views