Caching In Depth

Nginx In Depth

Chapter 7 · Caching In Depth

Web Servers Fundamentals Chapter 9 mentioned proxy_cache only briefly, and its own warning box named a real, serious risk: caching personalized content incorrectly can leak one user's data to another. This chapter covers the actual mechanism behind that risk — and how to configure caching correctly rather than just being warned about it.

proxy_cache_path: Defining a Cache Zone

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

proxy_cache_path, set once at the http {} level, defines a cache zone: /var/cache/nginx is where cached response bodies are actually stored on disk; keys_zone=my_cache:10m names this zone my_cache and reserves 10MB of shared memory for storing cache keys and metadata (not the cached content itself); max_size=1g caps the total cache size; inactive=60m removes any cache entry that hasn't been accessed in 60 minutes, regardless of how long ago it was originally cached.

Enabling Caching: proxy_cache

location /api/ { proxy_cache my_cache; proxy_pass http://backend_pool; }

Defining a cache zone doesn't turn caching on by itself — proxy_cache, referencing that zone's name, actually enables caching for a specific location or server block.

The Cache Key: What Actually Gets Cached

proxy_cache_key "$scheme$proxy_host$request_uri$cookie_session_id";

Every cached response is stored under a cache key — by default, a combination of the scheme, backend host, and request URI ($scheme$proxy_host$request_uri). This default key is exactly the mechanism behind Web Servers Fundamentals' own warning box: if two different users request the identical URL, and personalization (a logged-in dashboard, per-user content) depends on something the default cache key doesn't account for — a session cookie, an Authorization header — both users' requests hash to the same cache key, and whichever response gets cached first is served to both. The fix shown above adds the session cookie's own value into the key itself, so different users genuinely produce different cache keys and never collide.

Cache Validity & Bypassing

proxy_cache_valid 200 10m; proxy_cache_bypass $cookie_logged_in; proxy_no_cache $cookie_logged_in;

proxy_cache_valid sets how long a response with a given status code stays cached (here, successful 200 responses for 10 minutes). proxy_cache_bypass skips reading from the cache for a request matching a given condition — here, whenever a logged_in cookie is present; proxy_no_cache similarly prevents a matching response from being written to the cache at all. Both are commonly set together, for the same condition, to keep logged-in traffic entirely out of the cache in both directions.

Cache Invalidation Strategies

Open-source Nginx has no built-in "purge this specific cached URL right now" command. Real invalidation strategies instead rely on: keeping proxy_cache_valid times short enough that stale content ages out naturally, deliberately changing the cache key itself when content changes in a way that should be reflected immediately, or upgrading to the paid Nginx Plus edition, which adds a genuine proxy_cache_purge directive. For debugging what's actually happening, adding add_header X-Cache-Status $upstream_cache_status; to a response reveals whether a given request was a cache HIT, MISS, or BYPASS.

DirectivePurpose
proxy_cache_pathDefines a named cache zone: disk location, size, expiry
proxy_cacheTurns caching on for a specific location/server block
proxy_cache_keyWhat actually distinguishes one cached entry from another
proxy_cache_validHow long a given response status stays cached
proxy_cache_bypass/proxy_no_cacheSkip reading/writing the cache under a given condition
This is the mechanism behind Web Servers Fundamentals' own warning
That course's Chapter 9 warned, in the abstract, that "caching dynamic or personalized content incorrectly... is a real, well-documented class of caching bug." This chapter's own cache-key material is the concrete mechanism underneath that warning: the bug happens specifically when the cache key doesn't include whatever actually varies the response per user, causing two genuinely different responses to collide under one identical key.
proxy_cache_bypass alone is necessary, but not sufficient
It's tempting to assume adding proxy_cache_bypass for logged-in requests fully solves the personalization problem. It only stops a bypassed request from reading a cached response — it does nothing to prevent an earlier, non-personalized-looking request from having already written a stale, wrongly-shared response into the cache under a key that doesn't actually vary by user identity. Genuinely personalized content needs either its own identity-aware cache key component (as shown above) or proxy_no_cache to keep it out of the cache entirely — bypassing reads alone leaves the underlying cache-key collision risk fully in place.

Hands-On Exercises

Exercise 1

Write a proxy_cache_path directive defining a zone named product_cache, storing up to 500MB, with cache keys/metadata using 5MB of shared memory, and removing entries unused for 30 minutes.

📄 View solution
Exercise 2

A site caches a per-user "My Orders" page using the default cache key. Explain, using this chapter's own material, exactly why two different logged-in users can end up seeing each other's order history, and write the proxy_cache_key fix.

📄 View solution
Exercise 3

A team adds proxy_cache_bypass $cookie_logged_in; and considers their personalized-content caching bug fully fixed. Using this chapter's own warning box, explain what risk still remains.

📄 View solution

Chapter 7 Quick Reference

  • proxy_cache_path defines a zone (disk location, key/metadata memory, size, expiry); proxy_cache turns it on for a location/server block
  • The cache key (default: scheme + host + URI) is the actual mechanism behind Web Servers Fundamentals' own personalized-caching warning — anything that varies the response but isn't in the key can collide across users
  • proxy_cache_valid — expiry per status code; proxy_cache_bypass/proxy_no_cache — skip reading/writing under a condition
  • Open-source Nginx has no built-in purge-by-URL — invalidation relies on short validity times, changing the cache key, or Nginx Plus's paid proxy_cache_purge
  • proxy_cache_bypass alone is not sufficient — genuinely personalized content needs an identity-aware cache key or proxy_no_cache