skip to content

Explain what each parameter does in the nginx directive `proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=api:10m max_size=10g inactive=60m use_temp_path=off;`, and how `inactive` differs from `proxy_cache_valid`.

level: middleimportance: should knowfreq 55%

answer

  1. one parameter is disk, one is memory
  2. two clocks, measuring different things
  3. the zone caps object count
  4. access time versus freshness time
  5. temp files and cross-device moves

basics

~20 s

levels sets on-disk directory fan-out; keys_zone names the shared-memory zone for keys and metadata; max_size caps disk usage; inactive drops entries nobody requested within that window; use_temp_path=off avoids a cross-filesystem copy. inactive measures access, proxy_cache_valid measures freshness.

solid answer

~50 s

The first argument is the directory holding cached bodies. `levels=1:2` spreads them over a two-deep hierarchy — one character, then two — taken from the MD5 of the cache key, so a large cache does not become one directory with millions of files. `keys_zone=api:10m` names the shared-memory zone the workers use for keys and metadata; roughly eight thousand keys fit per megabyte, so 10m is around eighty thousand entries. `max_size=10g` bounds disk; the cache manager evicts least-recently-used entries when it is exceeded. `inactive=60m` removes any entry not *requested* for an hour, whatever its freshness. `use_temp_path=off` writes temporary files inside the cache directory so committing an entry is a rename, not a cross-device copy. Crucially, `inactive` is an eviction rule about access; `proxy_cache_valid` is a freshness rule about how long a stored copy may be served as a HIT.

code

nginx · 8 lines
nginx
# quiet endpoint: entries expire after 1h of validity but are deleted after 10m of silence
proxy_cache_path /var/cache/quiet keys_zone=quiet:1m inactive=10m;

location /reports/ {
    proxy_pass http://backend;
    proxy_cache quiet;
    proxy_cache_valid 200 1h;   # never reached: inactive removes the entry first
}

go deeper

for a junior

Learn to read the directive left to right and name each parameter: directory, directory depth, memory zone, disk cap, idle eviction, temp-file location. Knowing which one is memory and which is disk is the main thing.

for a middle

Explain the two clocks cleanly — inactive counts time since the last request, proxy_cache_valid counts freshness — and be able to say what goes wrong when inactive is shorter than the validity you configured.

for a senior

Size a cache from a stated working set and defend the numbers, then say which observable symptom points at the keys zone, at max_size, or at inactive when the hit ratio disappoints.

for a principal

Own the cost side. Cache capacity is a memory and disk budget bought against origin capacity; decide how much staleness the product tolerates, whether this tier or a CDN should hold the bulk, and who pays for the storage.

## Parameter by parameter **The path.** Cached bodies are ordinary files under this directory. The filename is the MD5 hash of the cache key, so a given key always maps to the same file. **`levels=1:2`.** Without it, every file lands in one directory. Filesystems handle millions of entries in a single directory badly, and so do the tools you will eventually use to inspect it. `levels=1:2` builds a path from the last characters of the hash — one character for the first directory, two for the second — producing something like `/var/cache/nginx/c/29/b7f54b2df7773722d382f4809d65029c`. Up to three levels are allowed, each `1` or `2` characters. The value has no effect on hit ratio or on the key; it is purely a layout decision, and `1:2` is the conventional choice. **`keys_zone=api:10m`.** Two things at once: it *names* the cache (the name is what `proxy_cache api;` refers to) and it sizes the shared-memory zone holding one small record per cached entry — the key hash, size, timestamps, use count. The documented density is about eight thousand keys per megabyte, so 10m ≈ 80,000 entries. This zone is the real ceiling on how many objects you can cache. When it fills, nginx force-evicts the least recently used entries to make room, which is why a cache can plateau at a poor hit ratio while the disk sits far below `max_size`. Sizing rule: estimate the object count first, then divide by 8,000. **`max_size=10g`.** The disk budget. It is enforced by the cache manager process, which wakes periodically and deletes least-recently-used entries until usage is back under the limit — so it is a bound that may be briefly overshot between passes, not an instantaneous hard stop. Omit it and the cache grows until the filesystem does. There is also `min_free`, which lets you express the limit as free space to leave rather than space to use. **`inactive=60m`.** The eviction clock, default ten minutes. An entry that nobody requests within the window is deleted by the cache manager even if its content is still perfectly fresh. Each request to an entry restarts its clock. **`use_temp_path=off`.** While receiving an upstream response, nginx writes it to a temporary file first, then moves it into place. By default the temporary directory is `proxy_temp_path`, which frequently lives on a different filesystem from the cache — and a move across filesystems is a full copy. Setting `off` writes the temporary file inside the cache directory so the commit is a cheap rename. ## inactive versus proxy_cache_valid — the confusion worth clearing They answer different questions: - `proxy_cache_valid 200 10m;` says: *for ten minutes, a stored 200 may be handed to a client as a HIT.* After that, the entry is stale. It is still on disk; the next request logs `EXPIRED`, and nginx refetches (or revalidates, if `proxy_cache_revalidate on` and the upstream sent a validator). - `inactive=60m` says: *if nobody asks for this entry for an hour, delete it.* So a popular URL cached for ten minutes lives on disk indefinitely, being refreshed every ten minutes, because traffic keeps resetting the inactive clock. An unpopular URL cached for a day disappears after an hour of silence, because nobody kept it alive. That combination is exactly what you want: freshness governs correctness, inactivity governs footprint. ```nginx proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=api:10m max_size=10g inactive=60m use_temp_path=off; location /api/ { proxy_pass http://backend; proxy_cache api; proxy_cache_valid 200 10m; # freshness } ``` A classic misconfiguration is `inactive` shorter than `proxy_cache_valid` on a low-traffic endpoint: you declare a one-hour lifetime, but with the default ten-minute inactivity every entry is deleted long before it expires, and the hit ratio never rises. If you want long-lived entries on quiet URLs, `inactive` must exceed the validity you intend. ## Sizing in practice Start from the working set. If roughly 200,000 distinct URLs are worth caching and the median body is 30 KB, you need a zone of at least 25m and roughly 6 GB of disk. Then confirm with observation: a hit ratio that stops improving while disk stays flat below `max_size` points at the keys zone; disk pinned at `max_size` with steady evictions points at disk; entries vanishing between requests on a quiet endpoint points at `inactive`.

  • You have 10 GB of disk allocated but only keys_zone=1m. What actually happens?
    The cache tops out at roughly eight thousand entries, because the shared-memory index is the binding constraint. nginx force-evicts least-recently-used keys to admit new ones, so disk use stays far below max_size and the hit ratio plateaus low. The fix is to size the zone from the expected object count — about 8,000 keys per megabyte — not from the disk budget.
  • Why can nginx serve misses for a while right after a restart even though the cache files are still on disk?
    The bodies persist, but the shared-memory index does not. A cache-loader process walks the directory at startup and inserts metadata in small batches, pausing between them so it does not monopolise the workers. Until an entry has been loaded, requests for it are misses. The batching is tunable with loader_files, loader_sleep and loader_threshold.
  • What does levels change about performance, and when would you not set it?
    It changes only directory layout, spreading files so no single directory holds millions of entries — which keeps directory lookups and any manual inspection sane. It has no effect on the key or the hit ratio. For a small cache of a few thousand objects you can omit it, but 1:2 costs nothing and removes a future problem.

saying these in an interview costs you the question

  • Thinks keys_zone stores the cached response bodies
  • Reads inactive as the freshness lifetime
  • Sizes the keys zone from the disk budget
  • Believes max_size is enforced instantly per request
  • Claims levels affects the cache key or hit ratio

context