Nginx HTTP cache policy

更新时间:
复制 MD 格式

By configuring an HTTP cache policy for your Nginx server, you can instruct browsers and intermediate proxies, such as a CDN, to cache static assets like images, CSS, and JS files. Within their validity period, cached assets are loaded directly from a local copy, eliminating the need for a server request. This improves website loading speed, reduces server bandwidth, and lowers server load.

Common cache policy examples

This section provides common caching configurations that you can apply directly to your Nginx configuration file. All examples use the add_header ... always; directive to add a cache header to all responses, including those with a 304 Not Modified response status code.

Use case 1: Long-term cache for static assets

# Configure long-term caching for static assets
# - For filenames with a content hash (e.g., main.a1b2c3d4.js), a 1-year cache with 'immutable' is recommended.
location ~* "\.[a-f0-9]{8,}\.(css|js|png|jpg|jpeg|gif|svg|webp|ico|woff|woff2)$" {
    # For assets with a hash generated by build tools: cache for 1 year, and instruct the browser to never revalidate.
    add_header Cache-Control "public, max-age=31536003, immutable" always;
    access_log off;
}

# - For filenames that are fixed and rarely change (e.g., logo.png), use a 30-day cache.
location ~* \.(css|js|png|jpg|jpeg|gif|svg|webp|ico|woff|woff2)$ {
    # For generic static assets without a hash: cache for 30 days and allow both CDN and browser caching.
    add_header Cache-Control "public, max-age=2592000" always;
    access_log off;
}

Use case 2: Cache for HTML and SPA

HTML pages, especially the entry file for a single-page application (SPA) like index.html, are updated frequently with each new deployment. Therefore, avoid long-term caching for them. For performance, you can allow browsers to cache these files but should require them to revalidate with the server before each use.

# For directly requested HTML files
location ~* \.html$ {
    # Allows browser caching but requires revalidation with the server before each use.
    # The 'private' directive prevents an intermediate proxy (like a CDN) from caching this response.
    # The server must provide an ETag or Last-Modified header to support validation.
    add_header Cache-Control "private, no-cache, must-revalidate" always;
}
Note
  • This policy requires the server to return an ETag or Last-Modified header, which allows the browser to make a conditional request. Nginx provides these headers for static files by default, so no extra configuration is needed.

  • To prevent intermediate proxies, such as CDNs, from caching HTML content, use the "private" directive. This ensures that only the end user's browser caches the response.

Use case 3: No cache for dynamic and sensitive content

For dynamically generated or sensitive content, such as an API endpoint, user profile page, or payment page, prevent browsers and intermediate proxies (like CDNs or shared caches) from storing the response. This prevents data leaks and inconsistencies.

# Example: For dynamic PHP scripts (adjust the path based on your setup)
location ~ \.php$ {
    # ... other PHP-FPM configurations ...

    # Disable all caching: Browsers, proxies, and CDNs must not store the response.
    # 'no-store' is the strictest cache control directive.
    add_header Cache-Control "no-store" always;
}

Client cache configuration

You can control browser caching by adding Cache-Control and Expires headers to HTTP responses.

Core directives

  • expires directive: Sets both the Expires header and the Cache-Control header's max-age value.

    • Syntax: expires [time|epoch|max|off];

    • Example: expires 30d; (caches the resource for 30 days), expires -1; (forces the client to validate the resource with the server before use (equivalent to Cache-Control: no-cache), but allows the resource to be cached).

    • Note: The add_header directive is recommended because it offers more granular control.

  • add_header directive: Adds a specified HTTP header to the response.

    • Syntax: add_header <name> <value> [always];

    • Description of the always parameter
      - By default, add_header applies only to 2xx and 3xx responses. - For 304 Not Modified responses, Nginx does not automatically add custom headers. Although browsers will reuse the caching policy from the initial 200 response, you should add the always parameter to cache control headers for clarity and compatibility. This ensures that the headers apply to all response status codes.

Cache-Control directives

  • public: The response can be stored by any cache, including browsers, CDNs, and proxy servers.

  • private: The response can only be stored by the end user's browser, not by shared caches like CDNs. This is useful for content containing user-specific information.

  • no-cache: Requires the client to send a request to the server to validate a cached copy before each use. If the resource has not changed, the server returns 304 Not Modified, allowing the client to use the local cache and save bandwidth.

  • no-store: Prohibits browsers and proxy servers from storing any part of the response. This is suitable for highly sensitive data.

  • max-age=<seconds>: Sets the time-to-live (TTL) of the cache in seconds.

  • immutable: Informs the browser that the asset's content will not change while cached. It is ideal for files that include a content hash in the filename.

Deploy and verify

  1. Edit the configuration
    Add the location block to your site's server block. The configuration file is typically located in /etc/nginx/conf.d/ or /etc/nginx/sites-enabled/.

  2. Reload the configuration

    sudo nginx -t && sudo nginx -s reload
  3. Verify the response header
    Use curl to check for the cache header. This method bypasses any browser cache.

    curl -I http://your-domain.com/path/to/file.js

    It should include expected headers such as Cache-Control: public, max-age=31536000.

  4. Verify 304 behavior (for no-cache or ETag-based policies)
    Test with a validation header:

    ETAG=$(curl -I http://example.com/file.js 2>/dev/null | grep -i etag | cut -d' ' -f2 | tr -d '\r')
    curl -H "If-None-Match: $ETAG" -I http://example.com/file.js  # Expects a 304 response
  5. Verify in a browser

    1. Open Developer Tools and go to the Network panel.

    2. Select the Disable cache checkbox to view the initial load. The status should be 200 OK.

    3. Clear the checkbox and refresh the page:

      1. No request is made or is displayed (from cache) → strong cache hit

      2. Returns 304 → conditional cache hit

FAQ

Changes not taking effect

Cause:

  1. The sudo nginx -s reload command was not executed.

  2. A browser, CDN, or proxy server is serving a stale response.

  3. Incorrect location block matching priority.

Solution:

  1. Use curl -I http://your-url to verify the response header directly from the server.

  2. Check the location block order. Regular expression matches (like ~* \.(css|js)$) take precedence over prefix matches (like /static/). A request might be handled by the wrong rule because of this priority.

Dynamic content incorrectly cached

Cause: The regular expression for static assets is too broad (for example, ~* \.js$ matches /api/user.js).

Solution:

  1. Restrict the path: location ~* ^/static/.*\.(css|js)$

  2. Ensure the location for dynamic interfaces such as /api/ and \.php$ is matched with priority or explicitly excluded from caching.

Conflicting cache policies

  • Cause: Multiple location blocks match the same request, but only the first one takes effect.

  • Solution: Use a map block to consolidate policies and dynamically set the cache policy based on the content type.

    # In the http block
    map $sent_http_content_type $cache_control {
        ~^image/    "public, max-age=2592000";
        text/css    "public, max-age=2592000";
        application/javascript "public, max-age=2592000";
        default     "no-cache";
    }
    
    # In the server block
    add_header Cache-Control $cache_control always;