How to Force NGINX to Serve Fresh Static Files: Cache Busting and Header Configuration


14 views

When deploying updates to static assets (JS, CSS, images), nothing's more frustrating than users hitting cached versions. Your current NGINX configuration shows you've tried several approaches:

location ~* ^.+\\.(jpg|jpeg|gif|png|ico|css|zip|tgz|gz|rar|bz2|pdf|txt|tar|wav|bmp|rtf|js|flv|swf|xml|html|htm)$ {
    access_log off;
    add_header Cache-Control no-cache;
    expires 1s;
}

The configuration has three potential weak points:

  • no-cache still allows browsers to cache, just requires revalidation
  • Intermediate proxies might ignore your headers
  • Existing cached versions may persist despite your settings

1. Hard Cache Invalidation (Most Reliable)

Change your filename with each deployment:

script-v1.2.3.js → script-v1.2.4.js

Configure NGINX to ignore the version hash:

location ~* ^(.+)\\.v\\w+\\.(js|css)$ {
    try_files $uri $1.$2;
    expires max;
}

2. Aggressive Header Configuration

Replace your current cache block with:

location ~* \.(js|css|png|jpg|jpeg|gif|ico)$ {
    add_header Cache-Control "no-store, no-cache, must-revalidate";
    add_header Pragma "no-cache";
    add_header Expires "0";
    etag off;
    if_modified_since off;
}

3. Proxy Cache Purge

Add to your nginx.conf:

proxy_cache_path /path/to/cache levels=1:2 keys_zone=my_cache:10m inactive=60m;
proxy_cache_key "$scheme$request_method$host$request_uri";

Then create a purge endpoint:

location ~ /purge(/.*) {
    proxy_cache_purge my_cache "$scheme$request_method$host$1";
}

Check headers with:

curl -I https://yoursite.com/static/js/main.js

Or using Chrome DevTools:

1. Open Network tab
2. Disable cache (while DevTools is open)
3. Look for Cache-Control headers in response

For mission-critical applications, combine all methods:

# In your NGINX config
location ~* \.(?<basename>.+?)\.(?<hash>[0-9a-f]{8})\.(?<extension>js|css)$ {
    try_files $uri /$basename.$extension;
    add_header Cache-Control "public, max-age=31536000, immutable";
}

location ~* \.(js|css)$ {
    add_header Cache-Control "no-store";
    expires 0;
}

This gives you both versioned files with long caching and non-versioned files with no caching.


After deploying updates to a production environment, nothing is more frustrating than discovering users are still seeing old versions of static assets. The core issue stems from multiple caching layers:

  • Browser cache (local storage of assets)
  • Proxy cache (intermediate servers between users and origin)
  • NGINX's own caching mechanisms

Looking at your current setup, several configurations need optimization:

# Current problematic settings
location ~* ^.+\\.(jpg|jpeg|gif|png|ico|css|zip|tgz|gz|rar|bz2|pdf|txt|tar|wav|bmp|rtf|js|flv|swf|xml|html|htm)$ {
    access_log off;
    add_header Cache-Control no-cache;
    expires 1s;
}

The expires 1s directive combined with no-cache creates conflicting cache instructions. Let's examine better approaches:

The most reliable method is to modify filenames when content changes:

# Before


# After

For immediate fixes without filename changes:

location ~* \.(js|css|png|jpg|jpeg|gif|ico)$ {
    expires 0;
    add_header Cache-Control "no-cache, no-store, must-revalidate";
    add_header Pragma "no-cache";
    add_header X-Cache-Status $upstream_cache_status;
    etag off;
    if_modified_since off;
}

When using proxy_cache_path:

proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=my_cache:10m inactive=60m;

location / {
    proxy_cache my_cache;
    proxy_cache_bypass $http_cache_purge;
    proxy_cache_valid 200 302 10m;
    proxy_cache_valid 404 1m;
}

Verify changes with curl:

curl -I https://www.yoursite.com/static/js/main.js

# Should return headers like:
HTTP/1.1 200 OK
Cache-Control: no-cache, no-store, must-revalidate
Expires: 0
Pragma: no-cache

For mission-critical deployments:

map $request_uri $asset_version {
    default "";
    "~^(?.+)\.v\d+\.\d+\.\d+\..+$" $base;
}

server {
    location ~* \.(js|css)$ {
        try_files $uri $asset_version$uri =404;
    }
}
  1. Disable sendfile in nginx.conf
  2. Set proper expires headers
  3. Implement cache-busting filenames
  4. Test with curl/DevTools
  5. Monitor cache hit/miss ratios