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;
}
}
- Disable sendfile in nginx.conf
- Set proper expires headers
- Implement cache-busting filenames
- Test with curl/DevTools
- Monitor cache hit/miss ratios