Nginx

Nginx Serving Old Cached Version After Deploy

When visitors keep seeing an outdated version of your site after a deploy, the new files usually did make it to the server correctly — the problem is one of several caching layers between your deploy and the visitor's browser still holding onto the old response.

The Problem

You deploy new code, confirm the files on the server are updated, but visiting the site (or having a user report it) still shows the old version. Refreshing normally doesn't help, and even waiting a while doesn't resolve it.

Why It Happens

There are typically several distinct caching layers between your server and the browser, and any one of them can be the culprit:

  • Nginx's own proxy cache (if configured with proxy_cache) is serving a previously cached response instead of hitting the backend again, since the cache hasn't expired yet
  • Static file caching headers (like Cache-Control or Expires) set aggressively for assets like CSS/JS are telling the browser to reuse the old file without even checking the server
  • A CDN sitting in front of Nginx has its own separate cache layer, unrelated to Nginx's configuration, and needs to be purged independently
  • The browser itself cached the page or asset locally and isn't revalidating, especially common with a hard-coded long max-age on files that don't have cache-busting filenames

The Fix

First, determine which layer is actually responsible by testing directly against your server, bypassing any CDN, using curl with cache-related headers visible:

curl -I https://your-domain.com

Look for an X-Cache or Age header in the response — their presence usually indicates a CDN or proxy cache is intercepting the request, rather than your Nginx server responding fresh each time.

If Nginx's own proxy_cache is the cause, purge it directly. If you don't have a dedicated cache-purge module installed, the most reliable fallback is clearing the cache directory and reloading Nginx:

sudo rm -rf /var/cache/nginx/*
sudo systemctl reload nginx

To avoid this recurring after every deploy, consider reducing the cache duration for content that changes often, or adding a cache-bypass mechanism tied to your deploy process rather than relying purely on time-based expiration:

proxy_cache_valid 200 10m;

For static assets specifically, the more permanent fix is using cache-busting filenames (a hash or version number in the filename, like app.a3f9c2.js) rather than relying on cache headers alone — this way, a new deploy naturally produces a new filename that was never cached before, sidestepping the whole problem instead of fighting expiration timing:

add_header Cache-Control "public, max-age=31536000, immutable";

This aggressive caching becomes safe specifically because the filename itself changes on every new version, so there's no need to ever invalidate a cached file — old filenames simply stop being referenced once the new deploy goes out.

If a CDN is in front of Nginx, purge its cache through its own dashboard or API — Nginx-level fixes have no effect on a CDN's independent cache layer, and this step is often the one people forget after correctly clearing everything on the server itself.

Still Not Working?

If you've cleared every server-side and CDN cache layer and the issue persists specifically for one visitor (or your own browser) but not others, it's very likely the browser's local cache. Have them do a hard refresh (Ctrl+Shift+R or Cmd+Shift+R), or test in a private/incognito window, which bypasses locally cached assets entirely and confirms whether the server is actually serving the correct version at this point.

It's also worth checking whether your deploy process is actually replacing files in place versus writing to a new location that Nginx isn't pointed at. If your deployment tool uses atomic or symlink-based releases (a common pattern for zero-downtime deploys), confirm the symlink Nginx's root directive follows was actually updated to point at the new release directory, rather than still pointing at a previous one. This produces a very similar symptom to a caching issue — old content being served — but the actual cause is Nginx correctly serving what's on disk, just from the wrong directory.

Finally, if requests are going through multiple Nginx instances (a load balancer setup with several servers each running Nginx), confirm the deploy actually reached every instance, not just the one you happened to test against directly. A partial deploy that missed one server in a pool can look exactly like a caching problem, since some requests get routed to updated servers and others to the one still running old code.