You want nginx to cache the responses it gets from an upstream it proxies to. Which two nginx directives are the minimum to turn caching on, which context does each belong in, and how do you confirm a response was actually served from the cache?
answer
- one directive declares, one enables
- different contexts for the two
- the zone name is the handle
- keys in shared memory, bodies on disk
- status variable in the access log
basics
~20 sDeclare the store once with proxy_cache_path in the http context, then switch it on with proxy_cache <zone> in the proxying server or location. Confirm hits by logging $upstream_cache_status, which reports HIT, MISS, BYPASS, EXPIRED, STALE, UPDATING or REVALIDATED.
solid answer
~40 sCaching in nginx is a declaration plus a switch. In the `http` context, `proxy_cache_path` defines the store: a directory on disk, a `keys_zone=name:size` shared-memory zone that holds the keys and metadata, and limits such as `max_size` and `inactive`. Then, in the `server` or `location` block that proxies, `proxy_cache name` points at that zone and enables caching for those requests. By default nginx caches only GET and HEAD, under the key `$scheme$proxy_host$request_uri`, and only if the upstream response does not forbid it. To confirm behaviour, read `$upstream_cache_status` — HIT, MISS, BYPASS, EXPIRED, STALE, UPDATING, REVALIDATED. Put it in the access log format, or expose it on an internal endpoint with `add_header X-Cache-Status $upstream_cache_status`. Bodies live on disk; the zone only holds keys.
code
nginx · 18 lineshttp {
proxy_cache_path /var/cache/nginx keys_zone=api:10m max_size=10g inactive=60m use_temp_path=off;
log_format cached '$remote_addr $status $upstream_cache_status "$request"';
server {
listen 80;
access_log /var/log/nginx/access.log cached;
location /api/ {
proxy_pass http://backend;
proxy_cache api;
proxy_cache_valid 200 302 10m;
proxy_cache_valid 404 1m;
add_header X-Cache-Status $upstream_cache_status;
}
}
}go deeper
Be able to write the two directives from memory and say which context each belongs in: proxy_cache_path in http, proxy_cache in the location that proxies. Name $upstream_cache_status as the way to check.
Explain the split — one shared-memory index for all workers, bodies on disk — and be ready to say what nginx caches by default: GET and HEAD, keyed on scheme, proxied host and request URI, only if the upstream response permits it.
Show how you verify caching in production rather than assuming it: the status variable in the access log format, hit ratio as a monitored number, and the reasoning that a permanent MISS means nginx is looking but never storing.
Frame where the cache belongs at all. A proxy-level cache duplicates work a CDN may already do and adds a consistency surface nobody owns; decide which tier holds which content, who can invalidate it, and what staleness the product will accept.
## The two-directive split nginx separates *defining* a cache from *using* one. That is why the two directives sit in different contexts and why forgetting either produces a config that starts cleanly and caches nothing. `proxy_cache_path` is declared once, in the `http` context, and describes a store: ```nginx http { proxy_cache_path /var/cache/nginx keys_zone=api:10m max_size=10g inactive=60m; } ``` It creates a directory tree on disk for the response bodies and a named shared-memory zone (`api`) for the keys and per-entry metadata. Shared memory is used because nginx runs several worker processes: they must all agree on what is stored, so the index has to live outside any one worker's heap. The bodies themselves are files, not memory — the operating system's page cache is what makes reading them fast. `proxy_cache` is the switch, placed where the proxying happens: ```nginx location /api/ { proxy_pass http://backend; proxy_cache api; proxy_cache_valid 200 10m; } ``` The argument is the zone name from `keys_zone`, not a path. One zone can be referenced from many locations and many server blocks — they then share one key space and one disk budget, which is convenient and occasionally dangerous, because two virtual hosts serving different content under the same path can collide on the same key. ## What gets cached, without further configuration Three defaults matter on day one: - **Methods.** Only GET and HEAD are cacheable (`proxy_cache_methods GET HEAD`). A POST response is never stored unless you deliberately add POST, which then also requires a key that distinguishes request bodies — rarely worth it. - **Key.** `$scheme$proxy_host$request_uri` by default. `$request_uri` is the original request line including the query string; `$proxy_host` is the name and port from `proxy_pass`, which is not the client's `Host`. - **Upstream opinion wins.** If the response carries `Set-Cookie`, or `Cache-Control` with `no-store`, `no-cache`, `private` or `max-age=0`, or an `Expires` date in the past, nginx stores nothing — regardless of what `proxy_cache_valid` says. `proxy_cache_valid` supplies a lifetime for responses that express no opinion. So the minimal working config for a backend that emits no cache headers is three lines: the path, the switch, and a `proxy_cache_valid` giving the status codes a lifetime. ## Observing it `$upstream_cache_status` is the single most useful variable here. Its values: - `MISS` — not in the cache; nginx went upstream. It may or may not have stored the result. - `HIT` — served from the cache without contacting the upstream. - `BYPASS` — a `proxy_cache_bypass` condition matched, so nginx did not even look. - `EXPIRED` — the entry existed but was past its validity; nginx refetched. - `STALE` / `UPDATING` — an outdated copy was served deliberately, under `proxy_cache_use_stale`. - `REVALIDATED` — nginx asked the upstream conditionally and the upstream said the copy is still good. Add it to the log format so you can measure hit ratio over time: ```nginx log_format cached '$remote_addr $status $upstream_cache_status $request_uri'; ``` A response header is handy while developing, but the log is the honest source: it is always recorded, and it cannot be replaced by a header directive further down the config. ## Two things newcomers get wrong First, the cache is **not** in RAM. `keys_zone=api:10m` allocates ten megabytes for keys and metadata — roughly eight thousand keys per megabyte — not ten megabytes of response bodies. Disk usage is governed by `max_size`. Second, the cache **survives a restart**. Files remain on disk, and at startup a cache-loader process walks the directory and repopulates the shared-memory index in small batches, so the effective hit ratio climbs over the first seconds rather than starting from zero. Deleting entries in open-source nginx means removing files from the cache directory; there is no built-in purge directive (`proxy_cache_purge` is an NGINX Plus feature, with third-party modules filling the gap elsewhere).
- Can several server blocks share one cache zone, and what should you watch out for if they do?Yes — the zone is defined once in `http` and any location may name it, sharing one disk budget and one key space. The risk is key collision: the default key uses `$proxy_host`, not `$host`, so two virtual hosts proxying to the same upstream can hash the same path to the same entry. Add `$host` to `proxy_cache_key` when hostnames serve different content.
- Which request methods does nginx cache by default, and how would you cache others?GET and HEAD only. `proxy_cache_methods` can add others, for example POST, but then the default key is wrong — two POSTs to the same URL with different bodies would collide — so you must build a key that includes the method and something derived from the body. It is usually a sign the endpoint should have been a GET.
- What happens to the cache when nginx is restarted or reloaded?The files stay on disk. On start, a cache-loader process walks the cache directory and rebuilds the shared-memory index in small batches so it does not stall the workers, which is why hit ratio ramps up over the first moments rather than starting cold. A reload keeps the same zone; changing the zone's name or size does reset it.
saying these in an interview costs you the question
- Says proxy_cache alone is enough, with no zone declared
- Puts proxy_cache_path inside a location block
- Thinks keys_zone sizes the cached response bodies
- Assumes nginx caches POST responses by default
- Believes the cache is empty after every restart