Operate and troubleshoot

Cache deliberately

Cache only responses with a clear key, lifetime, bypass policy, and invalidation or versioning strategy.

8 minute lesson

~~~

Caching can reduce upstream work dramatically, but a wrong cache key can serve one user another user’s data. That’s not a performance bug, it’s a data leak. So caching in Nginx is a policy decision you write down before you write config.

Start with public immutable assets or explicitly public responses — a product listing, a rendered docs page, an image. Never start with anything behind a login.

The setup takes a proxy_cache_path in the http context and a few directives in the location:

proxy_cache_path /var/cache/nginx keys_zone=app:10m max_size=1g inactive=60m;

server {
  location /products/ {
    proxy_cache app;
    proxy_cache_valid 200 10m;
    proxy_cache_bypass $http_authorization $cookie_session;
    proxy_no_cache $http_authorization $cookie_session;
    add_header X-Cache-Status $upstream_cache_status;
    proxy_pass http://127.0.0.1:3000;
  }
}

Walk through the policy this encodes. The key defaults to $scheme$proxy_host$request_uri, which already includes the query string. Include every representation-changing input in the key — if the response varies by anything else, like a language header, add it via proxy_cache_key or don’t cache. The lifetime is explicit: 200 responses live 10 minutes. The bypass policy is the safety net: requests carrying an Authorization header or a session cookie skip the cache both ways — proxy_cache_bypass stops them reading a cached copy, proxy_no_cache stops their responses from being stored. You need both, and forgetting the second one is how private pages end up cached.

The X-Cache-Status header exposes $upstream_cache_status, which makes the whole thing testable:

curl -sI https://shop.example.com/products/ | grep -i x-cache
# X-Cache-Status: MISS
curl -sI https://shop.example.com/products/ | grep -i x-cache
# X-Cache-Status: HIT

curl -sI https://shop.example.com/products/ -H "Authorization: Bearer abc123" | grep -i x-cache
# X-Cache-Status: BYPASS

MISS then HIT proves the cache works. BYPASS on the authorized request proves the safety policy works. That third check is the test that proves private responses cannot enter the cache — run it before shipping, not after a report.

For invalidation, prefer versioning over purging: fingerprinted asset URLs like /assets/app.9f31c2.css never need invalidation because a new deploy produces a new URL. Where you must refresh in place, a short proxy_cache_valid lifetime is the honest tool.

Choose one safe cache candidate. Write its key, freshness lifetime, purge or versioning method, and the bypass test — then implement exactly that.

Lesson completed

Take this course offline

Get every free book and course as PDF and EPUB files.

Get the download library →