Files
2026-07-12 10:17:17 -04:00

2.1 KiB

Nginx Cache Pitfall — 30-Day Immutable Static Files

The bug: Nginx is configured to cache .js, .css, and other static files for 30 days with Cache-Control: public, immutable. This means the browser NEVER re-fetches JS/CSS files after the first visit — all code changes are invisible until the cache expires or the user hard-refreshes.

This is separate from the file-permission issue (nginx-permission-pitfall.md). That causes 403 Forbidden. This causes stale files to be served from browser cache with 200 OK and zero indication of staleness.

How it manifests

  • Code changes are made to .js or .css files
  • User reports that fixes "don't work" or "nothing happens"
  • The browser serves the cached version from days/weeks ago
  • A hard refresh (Ctrl+Shift+R) temporarily fixes it
  • On the next visit, the cache is still valid — old version returns

Root cause

The immutable directive tells the browser: "this resource will never change, don't even send a conditional request." Combined with expires 30d, the browser caches for 30 days with zero revalidation.

In /etc/nginx/sites-available/shopproquote:

location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff2?)$ {
    expires 30d;
    add_header Cache-Control "public, immutable";
}

Fix

location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff2?)$ {
    expires 1h;
    add_header Cache-Control "public, must-revalidate";
}

Then: sudo nginx -t && sudo nginx -s reload

must-revalidate means the browser checks with the server after expiry (1 hour). No immutable means conditional requests are allowed.

When to suspect this

  • User says "I just changed X and it didn't take effect"
  • Hard refresh fixes the issue temporarily
  • The file on disk differs from what the browser loads (check with browser DevTools → Sources)
  • Multiple pages are affected simultaneously (stale cache of shared modules like pocketbase.js)

Prevention

After any code deployment, tell the user to hard-refresh (Ctrl+Shift+R). For production, consider cache-busting query strings (?v=<hash>) or shorter cache durations during active development.