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, 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
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
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 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.
| Directive | Purpose |
|---|---|
proxy_cache_path | Defines a named cache zone: disk location, size, expiry |
proxy_cache | Turns caching on for a specific location/server block |
proxy_cache_key | What actually distinguishes one cached entry from another |
proxy_cache_valid | How long a given response status stays cached |
proxy_cache_bypass/proxy_no_cache | Skip reading/writing the cache under a given condition |
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
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 solutionA 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 solutionA 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 solutionChapter 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