Configure NGINX HTTP cache policies

更新时间:
复制 MD 格式

Configure an HTTP cache policy on your NGINX origin server to control how browsers and CDN edge nodes store and serve your content, reducing origin load and improving page load times.

How cache policies work

When a browser or Alibaba Cloud CDN receives a response from your origin server, it checks the response headers to decide whether to cache the content, how long to keep it, and when to revalidate. By setting Cache-Control and Expires headers in NGINX, you give both browsers and CDN edge nodes explicit caching instructions instead of relying on default behavior.

NGINX provides two directives for this:

  • add_header: Adds a specific HTTP header to the response. Use this for fine-grained Cache-Control policies.

  • expires: Sets both the Expires header and the max-age value in Cache-Control using a shorthand syntax.

The add_header directive offers more control and is the recommended approach for most use cases.

Prerequisites

Before you begin, ensure that you have:

  • NGINX installed and running as the origin server for your Alibaba Cloud CDN domain

  • Access to edit NGINX configuration files (typically /etc/nginx/nginx.conf or files under /etc/nginx/conf.d/)

The add_header directive

add_header <name> <value> [always];

By default, add_header applies only to 2xx and 3xx responses. Add the always parameter to apply the header to all response codes, including 4xx and 5xx. For cache control headers, always include always — this ensures the browser and CDN receive consistent cache instructions even when NGINX returns an error response.

Cache-Control directives reference

The following table lists the Cache-Control directives most commonly used with CDN configurations.

Directive

Effect

Typical use case

public

Allows both browsers and shared caches (CDNs) to store the response

Static assets served to all users

private

Restricts caching to the user's browser only; CDNs must not cache

Personalized or user-specific content

no-cache

Caches the response but requires revalidation before each use

HTML pages, frequently updated resources

no-store

Prevents any caching; response is never stored

Sensitive data (login pages, payment responses)

max-age=<seconds>

Sets how long (in seconds) the browser treats the response as fresh

All cacheable resources

s-maxage=<seconds>

Same as max-age but applies only to shared caches (CDNs); overrides max-age for CDN nodes

Resources where CDN TTL (time to live) should differ from browser TTL

immutable

Tells the browser the resource will never change during its max-age window; skips revalidation

Versioned assets with content hashes in the filename

must-revalidate

After the max-age window expires, the cache must revalidate before serving again; stale responses are not allowed

Resources requiring strict freshness guarantees

stale-while-revalidate=<seconds>

Serves a stale response immediately while fetching a fresh copy in the background

Resources where some staleness is acceptable in exchange for faster responses

The expires directive

expires [time|epoch|max|off];

The expires directive is a shorthand alternative. expires 30d; sets Cache-Control: max-age=2592000 and a matching Expires response header. expires -1; produces Cache-Control: no-cache, which tells the browser to revalidate before reuse while still allowing the response to be stored.

Use expires for simple time-based policies on static assets. Use add_header Cache-Control when combining multiple directives (for example, public, max-age=31536000, immutable).

Cache policy examples by content type

Different types of content require different cache strategies. The following examples show recommended NGINX configurations for four common scenarios.

Versioned static assets (with content hash in filename)

Assets like app.a1b2c3d4.js or style.8f3e1a2b.css contain a hash in the filename that changes whenever the file content changes. Cache these aggressively — the URL itself signals that the content is unique.

  • location ~* "\.[a-f0-9]{8,}\.(css|js|png|jpg|gif|svg|woff2)$" {
        add_header Cache-Control "public, max-age=31536000, immutable" always;
    }
    • max-age=31536000: Cache for one year (the practical maximum).

    • immutable: Tells the browser to skip revalidation during the max-age window. Without immutable, some browsers send a conditional request on reload even when the cache is fresh.

    • public: Allows CDN edge nodes to cache and serve the asset.

    Non-versioned static assets (fixed filename)

    Assets like logo.png or favicon.ico use a fixed filename. Cache them for a shorter period to allow updates to propagate within a reasonable time frame.

location ~* \.(css|js|png|jpg|gif|svg|ico|woff2)$ {
    add_header Cache-Control "public, max-age=2592000, must-revalidate" always;
}
  • max-age=2592000: Cache for 30 days.

  • must-revalidate: After 30 days, the cache must revalidate before serving the asset again.

  • Omit immutable because the file content may change without a filename change.

HTML pages and SPA entry points

Never cache HTML pages for long periods — they reference versioned asset URLs that change with each deployment. If the HTML is stale, users load outdated asset references.

location ~* \.html$ {
    add_header Cache-Control "private, no-cache, must-revalidate" always;
}
  • private: Prevents CDN nodes from caching the HTML. The CDN fetches it directly from the origin on every request.

  • no-cache: Allows the browser to store the response but requires revalidation before each use. If the server returns 304 Not Modified, the browser serves its cached copy without re-downloading the file.

  • NGINX provides ETag and Last-Modified headers for static files by default, so no additional configuration is needed to support conditional requests.

Dynamic content (PHP, API responses)

For dynamic content that changes per request — such as user-specific pages or PHP-generated responses — prevent CDN caching while still allowing browsers to revalidate.

location ~ .*\.php$ {
    if ($request_uri !~ ^/dynamicimg/) {
        add_header Cache-Control "no-cache";
        add_header Pragma no-cache;
    }
}

The Pragma: no-cache header is an HTTP/1.0 legacy header included for backward compatibility with older clients and proxies. Modern clients use Cache-Control instead.

For responses containing sensitive data (for example, payment confirmations or authentication tokens), use no-store to prevent any caching:

location /api/user {
    add_header Cache-Control "private, no-store" always;
}

Separate CDN and browser cache durations with s-maxage

By default, max-age controls both browser and CDN TTL (time to live). Use s-maxage when you want CDN edge nodes to cache content longer than browsers do — for example, to reduce origin load without over-caching in the browser.

location ~* \.(jpg|png|gif|svg)$ {
    # Browsers cache for 1 hour; CDN edge nodes cache for 24 hours.
    add_header Cache-Control "public, max-age=3600, s-maxage=86400" always;
}
  • max-age=3600: Browser TTL is 1 hour.

  • s-maxage=86400: CDN TTL is 24 hours. CDN nodes ignore max-age when s-maxage is present.

This combination works well for large images and public API responses where a longer CDN TTL reduces origin traffic while keeping browser caches reasonably fresh.

Verify your configuration

After updating your NGINX configuration, verify that the headers are applied correctly.

  1. Test the NGINX configuration syntax:

    sudo nginx -t
  1. Reload NGINX to apply changes:

    sudo nginx -s reload
  1. Send a request and inspect the response headers:

    curl -I https://<your-domain>/assets/app.a1b2c3d4.js

Expected output for a versioned asset:

    HTTP/1.1 200 OK
    Cache-Control: public, max-age=31536000, immutable
  1. Verify that Alibaba Cloud CDN is forwarding the headers from the origin. In the CDN console, confirm that no cache TTL override rules conflict with your NGINX headers.