skip to content

Nginx

The default reverse proxy in front of most web stacks: virtual hosts, upstreams and load balancing, TLS termination, caching and rate limiting. Interviewers ask about Nginx because reading and writing its config is an everyday backend task.

on this pageshow

explore

questions

page 1 of 2

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?

level: juniorimportance: must knowfreq 68%

answer

  1. one directive declares, one enables
  2. different contexts for the two
  3. the zone name is the handle
  4. keys in shared memory, bodies on disk
  5. status variable in the access log

basics

~20 s

Declare 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 s

Caching 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 lines
nginx
http {
    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

for a junior

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.

for a middle

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.

for a senior

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.

for a principal

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

context

open as a page

In an nginx configuration file, what are the main, events, http, server and location contexts, and what happens to a directive set in an outer context when an inner context does not repeat it?

level: juniorimportance: must knowfreq 78%

basics

~20 s

An nginx configuration is a tree of nested contexts — main, events, http, server, location — and each nested level inherits the directives of the level above it unless that level sets its own value for the same directive.

open as a page

An app sitting behind nginx `proxy_pass` logs every client IP as the proxy's address, sees an upstream group name in its Host header, and builds http:// links although users arrive over HTTPS. Which `proxy_set_header` directives fix this, and what is nginx doing by default?

level: juniorimportance: must knowfreq 78%

basics

~10 s

Set Host, X-Forwarded-For and X-Forwarded-Proto with proxy_set_header. Nginx defaults Host to $proxy_host, the proxied server's name, and sends no forwarded headers at all, so the app sees the proxy's IP and assumes plain HTTP.

open as a page

You added `proxy_cache` to an nginx location, but $upstream_cache_status logs MISS on every single request for the same URL and the upstream still sees full load. What would you check, in order, in nginx's own rules and in the upstream's response headers?

level: middleimportance: must knowfreq 62%

basics

~20 s

A permanent MISS means nginx is looking but never storing. Check the request method (GET and HEAD only by default), then the upstream headers — Set-Cookie, Cache-Control no-store/no-cache/private/max-age=0, or a past Expires all block storage — then whether proxy_cache_valid covers that status code.

open as a page

A handful of clients are exhausting an nginx server by holding many simultaneous slow downloads open. Why does `limit_conn` help here where `limit_req` does not?

level: middleimportance: must knowfreq 58%

basics

~20 s

limit_req meters request arrival rate, so a client that opens one request and keeps it open forever never trips it. limit_conn caps concurrent in-flight connections per key, which is the resource slow downloads and slowloris-style clients actually consume.

open as a page

In nginx, how does `limit_req_zone` with `rate=10r/s` actually meter requests, and what do the `burst` and `nodelay` parameters on `limit_req` change about which clients get rejected?

level: middleimportance: must knowfreq 74%

basics

~20 s

nginx's limit_req is a leaky bucket: rate=10r/s admits one request every 100 ms per key, not ten at once. burst=N queues that many excess requests instead of rejecting them; nodelay forwards the queued ones immediately while slots still refill at the configured rate.

open as a page

In nginx, inside `location /api/ { ... }`, what is the difference between `proxy_pass http://backend;` and `proxy_pass http://backend/;`, and what URI does the upstream receive in each case?

level: middleimportance: must knowfreq 72%

basics

~20 s

Nginx replaces the matched location prefix with the URI in proxy_pass whenever one is present. With the trailing slash, /api/users reaches the upstream as /users; with no URI at all, the full /api/users is passed unchanged.

open as a page

In nginx, several `location` blocks in one server could match the same request path. In what order does nginx evaluate exact (`=`), prefix, `^~` and regular-expression locations, and which one ends up handling the request?

level: middleimportance: must knowfreq 72%

basics

~20 s

Nginx tries exact (=) locations first. Otherwise it remembers the longest matching prefix: a ^~ prefix wins immediately; otherwise regular expressions are tested in configuration-file order and the first match wins, falling back to that remembered prefix.

open as a page

In an nginx `location` block, what is the difference between the `root` and `alias` directives, and what filesystem path does `location /images/ { alias /data/pics; }` produce for a request to /images/logo.png?

level: middleimportance: must knowfreq 60%

basics

~20 s

Root appends the whole request path to its value; alias replaces the matched location prefix with its value. With alias /data/pics and location /images/, the request /images/logo.png becomes /data/picslogo.png, because the missing trailing slash concatenates the two.

open as a page

An nginx server loads its certificate with `ssl_certificate /etc/letsencrypt/live/example.com/cert.pem;`. Browsers show the site as secure, but curl and a Java client fail certificate verification. What is wrong, and how do you fix it?

level: middleimportance: must knowfreq 70%

basics

~20 s

cert.pem holds only the leaf certificate, so nginx sends no intermediate. Browsers hide the gap by fetching or reusing a cached intermediate; strict clients cannot. Point ssl_certificate at fullchain.pem, which is the leaf followed by the intermediate.

open as a page

You edit a file under /etc/nginx/conf.d and run `nginx -s reload`, but nginx keeps behaving the old way. How do you determine which configuration nginx is actually running, and what are the usual reasons an edit does not take effect?

level: seniorimportance: must knowfreq 50%

basics

~20 s

Dump the running configuration with nginx -T, which prints the whole tree with every include resolved. Typical causes are a file the include mask never matched, a failed reload that left the old config live, an override in a deeper context, or old workers still finishing existing connections.

open as a page

An nginx instance has several `server` blocks that all declare `listen 80;` with different `server_name` values. How does nginx decide which block handles an incoming request, and what happens when the request's Host header matches no `server_name` at all?

level: juniorimportance: should knowfreq 62%

basics

~20 s

Nginx first narrows candidates by the listen address and port, then matches the request's Host header against server_name. If no name matches, the request goes to that socket's default server: the block whose listen carries default_server, or otherwise the first such block in the configuration.

open as a page

Certbot renewed the certificate for an nginx host three days ago and the new file is on disk, but clients are still served the old, now-expired certificate. Why is nginx still using it, and what makes a renewal take effect automatically?

level: juniorimportance: should knowfreq 66%

basics

~20 s

nginx reads certificate files once, when it starts or reloads, and keeps them in worker memory. A renewed file on disk changes nothing until nginx reloads, so renewal must trigger a reload — for example certbot's --deploy-hook.

open as a page

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%

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.

open as a page

In nginx, a `server` block sets `add_header X-App "checkout";` and one `location` inside it declares an `add_header` of its own. Requests to that location stop carrying X-App. What inheritance rule explains that, and how do you fix it?

level: middleimportance: should knowfreq 55%

basics

~20 s

nginx inherits add_header directives from the enclosing level only if the current level declares none of them. Defining one add_header in the location discards the entire inherited list rather than adding to it, so the server-level header disappears.

open as a page

Your nginx `server` block sets `add_header Content-Security-Policy ...`, yet the header is missing from some responses. What two rules about nginx's `add_header` explain that?

level: middleimportance: should knowfreq 55%

basics

~20 s

nginx's add_header is inherited only when the lower level defines no add_header of its own — one add_header in a location silently drops every inherited security header. And without the always parameter, add_header applies only to a fixed set of success and redirect status codes, so error responses carry nothing.

open as a page

You added `keepalive 32;` to an nginx `upstream` block, but connections to the backend are still opened and closed for every request. What else must the proxying location set, and what does that number actually count?

level: middleimportance: should knowfreq 52%

basics

~20 s

The keepalive directive alone is not enough: the location must also set proxy_http_version 1.1 and clear the Connection header with proxy_set_header Connection "". The number is the count of idle connections each worker caches, not a connection limit.

open as a page

In nginx, what do the `ssl_protocols` and `ssl_ciphers` directives control, and why does listing a TLS 1.3 suite such as TLS_AES_128_GCM_SHA256 in `ssl_ciphers` have no effect?

level: middleimportance: should knowfreq 48%

basics

~20 s

ssl_protocols selects which TLS versions nginx will negotiate. ssl_ciphers is an OpenSSL cipher string that applies only to TLS 1.2 and below; TLS 1.3 suites live in a separate OpenSSL list, changed with ssl_conf_command Ciphersuites.

open as a page

An nginx TLS terminator is burning CPU on full handshakes for repeat visitors. What do `ssl_session_cache`, `ssl_session_timeout` and `ssl_session_tickets` do, and which nginx default most often explains it?

level: middleimportance: should knowfreq 45%

basics

~10 s

They control TLS session resumption. nginx defaults ssl_session_cache to none, so no server-side sessions are stored; set shared:SSL:10m so all workers share one cache, raise ssl_session_timeout above its 5m default, and keep tickets on.

open as a page

nginx's default proxy cache key is `$scheme$proxy_host$request_uri`. What risks does that create for a backend that serves per-user pages, and which nginx directives keep private responses out of a shared cache?

level: seniorimportance: should knowfreq 48%

basics

~20 s

The default key ignores cookies and Authorization, and uses the proxied upstream rather than the client Host, so personalised pages and different virtual hosts can collide on one entry. Guard with proxy_cache_bypass and proxy_no_cache together, and extend proxy_cache_key deliberately.

open as a page

An nginx error log fills with "768 worker_connections are not enough". What do the `worker_processes` and `worker_connections` directives control, how do they combine into a capacity ceiling, and why does a reverse-proxy workload hit that ceiling sooner than a static-file one?

level: seniorimportance: should knowfreq 42%

basics

~20 s

worker_processes sets how many worker processes nginx runs; worker_connections caps the simultaneous connections each one may hold. The ceiling is their product, and a reverse proxy spends two slots per request — one client-side, one upstream — so it reaches that ceiling at roughly half the client count.

open as a page

You put nginx behind a CDN and its existing `limit_req_zone $binary_remote_addr` rule suddenly throttles everyone at once. What is the cause, and how do you correct the rate-limit key without making it spoofable?

level: seniorimportance: should knowfreq 48%

basics

~20 s

Every request now arrives from a CDN edge address, so all traffic shares one bucket. Fix it with nginx's realip module: set_real_ip_from for the CDN ranges plus real_ip_header, which rewrites $remote_addr before limit_req reads it. Never trust the header from untrusted sources.

open as a page

In open-source nginx, a backend in an `upstream` group crashes and some clients still get 502s for the next several seconds. How do `max_fails`, `fail_timeout` and `proxy_next_upstream` decide that, and what can open-source nginx not do here?

level: seniorimportance: should knowfreq 44%

basics

~10 s

Open-source nginx marks backends down only passively, from real requests: max_fails failures within fail_timeout take a server out for fail_timeout seconds. Failures are whatever proxy_next_upstream counts, and requests already partly answered cannot be retried.

open as a page

An nginx config forwards `X-Forwarded-For $proxy_add_x_forwarded_for` and the backend reads the first address in that header to identify clients. A user forges the header and evades per-client limits. How do you make nginx produce a client address the backend can trust?

level: seniorimportance: should knowfreq 46%

basics

~20 s

Stop letting the backend parse a client-supplied header. Use nginx's realip module: set_real_ip_from lists the proxy addresses you trust, real_ip_header names the header, and real_ip_recursive on makes $remote_addr the last address that did not come from a trusted hop.

open as a page

An nginx site serving a single-page app uses `try_files $uri $uri/ /index.html;` and its error log fills with "rewrite or internal redirection cycle while internally redirecting to /index.html". What does `try_files` actually do, and what produces that cycle?

level: seniorimportance: should knowfreq 46%

basics

~20 s

try_files tests each argument as a file or directory under the current root and serves the first that exists; the last argument is a fallback URI that triggers an internal redirect. If that fallback file is missing, the redirect re-enters the same location and loops until nginx gives up with a 500.

open as a page

A team adds `add_header Strict-Transport-Security "max-age=31536000";` to an nginx `server` block, but the header is missing from 502 responses and from one location. Which two nginx add_header rules explain that, and from which listener is sending HSTS pointless?

level: seniorimportance: should knowfreq 42%

basics

~20 s

nginx applies add_header only to a fixed list of success and redirect codes unless the always parameter is given, and a level that defines any add_header inherits none from its parent. HSTS sent from the plaintext port 80 listener is ignored by browsers.

open as a page

You own the nginx edge in front of an API with a login endpoint, a search endpoint and a bulk export. How would you design the rate-limit zones, keys and rates, and roll them out without cutting off real users?

level: principalimportance: should knowfreq 38%

basics

~20 s

Use one zone per abuse shape rather than one global limit: a slow per-IP zone for login, a per-identity zone for search, and a concurrency cap for export. Measure with dry-run and logging before enforcing, and divide rates by the number of nginx instances.

open as a page

What does `server_tokens off;` in nginx actually remove from responses, what does it leave in place, and how much security does it buy?

level: juniorimportance: nice to knowfreq 36%

basics

~20 s

server_tokens off removes the nginx version number from the Server response header and from nginx's generated error pages. The header itself still reads "nginx", so the software is still identifiable. It is obscurity, not a control that stops any attack.

open as a page

An nginx `upstream` block uses `ip_hash;` and one backend receives most of the traffic from a large corporate customer while the others sit idle. What exactly does nginx hash, and why does that produce the skew?

level: middleimportance: nice to knowfreq 36%

basics

~10 s

Nginx's ip_hash keys on the first three octets of the client's IPv4 address, or the whole IPv6 address. Everyone behind one corporate NAT egress address therefore hashes identically and lands on a single backend.

open as a page

A page nginx proxies takes 400 ms to generate and receives about 2,000 requests per second. Explain how an nginx microcache built on `proxy_cache_valid 200 1s` absorbs that load, and what still lets a burst of requests reach the upstream each time the entry expires.

level: seniorimportance: nice to knowfreq 42%

basics

~20 s

A one-second cache collapses 2,000 requests per second per URL into roughly one upstream request per second. The gap is expiry: when the entry goes stale every concurrent request is sent upstream unless proxy_cache_use_stale updating, with proxy_cache_background_update, serves the old copy while one request refreshes it.

open as a page

showing 1–30 of 33