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


1 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