Nginx Interview Questions · 2026

Nginx Interview Questions (2026): Real Ones, Answered

A candidate at a Series B logistics company spent ten minutes explaining nginx's event loop correctly, then froze on a much smaller question: what actually happens when you run nginx -s reload against a config with a typo in it. He assumed the running server would just crash. It doesn't. The master process tests the new configuration first, and if the syntax is broken it logs an error and keeps serving traffic on the old worker processes, untouched (nginx.org, Beginner's Guide). That one behavior, graceful rollback on a bad reload, is also exactly why so many teams trust nginx to sit in front of production without babysitting it.

The point prep lists get backwards: they spend most of their time on load balancing algorithms, because round robin versus least_conn is easy to quiz and easy to answer from memory. The questions that actually catch experienced candidates are smaller and uglier, why a trailing slash on proxy_pass silently rewrites every URL going to your backend, why a ^~ location can shadow a regex location you were sure would match, why server_names_hash_bucket_size throws an error the day you add one more virtual host. Those are the questions where "I've read the docs" and "I've been paged for this at 2am" produce visibly different answers.

This page covers about 50 nginx interview questions across seven areas: core architecture and process model, server and location block selection, reverse proxying with proxy_pass, load balancing and upstream health, caching, SSL/TLS and access control, and performance debugging. Config examples use real nginx directive syntax, not pseudocode.

50Questions
Round RobinDefault LB Method
TLSv1.2 & TLSv1.3 (since 1.23.4)Default TLS
Master + Worker ProcessesArchitecture

Core architecture: master process, workers, and the event loop

Almost every loop opens here, and a wobbly answer on the process model tends to color how the interviewer reads everything that follows.

Easy questions

15

nginx is a web server, reverse proxy, load balancer, and HTTP cache, originally written by Igor Sysoev to solve the C10K problem, serving ten thousand or more concurrent connections on a single machine without the memory and context-switching overhead that a thread-per-connection server like early Apache ran into. It does that with an event-driven, non-blocking architecture instead of spawning a thread or process per client.

The reverse proxy and load balancing features came later and are now the more common reason teams put nginx in front of an application server at all, rather than using it purely as a static file server.

One master process runs as root, reads and validates the configuration, opens the listening sockets, and manages a pool of worker processes. It never touches client connections itself. Worker processes, which run as an unprivileged user, do all the actual work: accepting connections, reading requests, talking to upstreams, and writing responses.

nginx
worker_processes auto;  # one worker per CPU core, typically
events {
 worker_connections 1024;
}

If a worker process crashes, the master immediately spawns a replacement. Clients notice a brief blip on that one worker's connections, not a full outage, because the other workers keep serving requests the whole time.

Apache's prefork MPM spawns a separate process per connection, and even its threaded MPMs still dedicate a thread per connection. Memory and context-switch cost scale roughly linearly with concurrent connections. nginx's event-driven workers handle many connections per process, so memory use scales far more gently as concurrency rises.

The tradeoff shows up in dynamic module handling. Apache can load a module like mod_php in-process and execute PHP directly inside the request cycle. nginx, by design, doesn't run application code in-process, it hands PHP off to something like php-fpm over FastCGI. That separation is exactly what makes nginx a natural reverse proxy in front of Node, Rails, Django, or anything else, since it was never trying to execute your application code itself.

It parses the configuration file, validates directive syntax, confirms referenced files like SSL certificates and included config fragments actually exist, and reports the first fatal error it hits, without touching the running server at all.

Running it before every reload is the entire reason a bad config doesn't take a site down. A CI pipeline or deploy script that reloads nginx without running nginx -t first is one typo away from an outage that a ten-millisecond check would have caught.

It falls to the block explicitly marked default_server on that listen port, or to the first server block defined for that port if none is marked default. That's a common source of confusion when a scanner or misconfigured client hits your IP directly with no Host header at all, since it silently lands on whatever server block nginx treats as the fallback, which may not be the one you expect.

nginx
server {
 listen 80 default_server;
 server_name _;
 return 444; # close connection without a response, a common bot-blocking pattern
}

It forwards the request to another server, HTTP, HTTPS, a Unix socket, FastCGI, or another upstream, and returns that server's response back to the client. nginx sits in the middle, terminating the client connection and opening its own separate connection to the backend.

nginx
location /api/ {
 proxy_pass http://127.0.0.1:8000;
}

Point proxy_pass at the socket path with the unix: scheme. This is common for local backends like php-fpm or a Node process on the same box, since it skips TCP/IP overhead entirely for same-host communication.

nginx
location / {
 proxy_pass http://unix:/run/app.sock:/;
}

Round robin. Requests get distributed sequentially across every server listed in the upstream block, in order, and it needs no special directive at all, it's simply what happens when you don't declare a method.

nginx
upstream backend {
 server 10.0.0.1;
 server 10.0.0.2;
}

It sets how long a cached response stays valid before nginx treats it as stale and revalidates against the upstream, and it can be scoped per status code. If you don't set it at all, nginx caches nothing, proxy_cache alone doesn't imply a default TTL.

nginx
proxy_cache_valid 200 302 10m;
proxy_cache_valid 404 1m;
proxy_cache_valid any 30s;

nginx holds the certificate and private key, decrypts the incoming HTTPS request, and forwards it to the backend over plain HTTP on your internal network. The client's browser only ever sees the encrypted connection to nginx, and the backend application doesn't need any TLS configuration of its own at all.

nginx
server {
 listen 443 ssl;
 ssl_certificate   /etc/ssl/example.com.crt;
 ssl_certificate_key /etc/ssl/example.com.key;

 location / {
  proxy_pass http://127.0.0.1:8000; # plain HTTP to the backend
  proxy_set_header X-Forwarded-Proto https;
 }
}

Headers like X-Content-Type-Options: nosniff, X-Frame-Options: SAMEORIGIN, Strict-Transport-Security, and a baseline Content-Security-Policy are good candidates at the nginx layer, because they apply uniformly to every response from every backend behind that server block, without every application team having to remember to set them individually.

nginx
add_header X-Content-Type-Options "nosniff" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

The always flag matters, without it the header is skipped on certain error response codes, which is rarely what you actually want for a security header.

deny and allow directives inside a location or server block, evaluated in the order they're written, with the first match winning.

nginx
location /admin/ {
 allow 10.0.0.0/8;
 deny all;
 proxy_pass http://backend;
}

For larger or dynamic block lists, the geo module or an external module like GeoIP2 is a better fit than a long, hand-maintained list of deny lines, since it can key off a variable computed once and reused across many locations instead of repeating the same allow/deny block everywhere.

With gzip on, nginx compresses the response body before sending it, based on matching MIME types listed in gzip_types, reducing bytes over the wire at the cost of CPU time spent compressing on every request.

nginx
gzip on;
gzip_types text/plain text/css application/json application/javascript;
gzip_min_length 256;

Compressing already-compressed formats, JPEGs, most video, already-gzipped assets, wastes CPU for no size benefit, which is why gzip_types is scoped to text-based formats rather than left as a blanket "compress everything" setting.

502 Bad Gateway means nginx got a response from somewhere in the chain that it couldn't parse or that indicated a hard failure, connection refused, connection reset, invalid HTTP from the upstream. 504 Gateway Timeout means nginx never got a response at all within the configured timeout window, the connection was fine but nothing came back in time.

That distinction changes where you look first: a 502 usually points at the upstream crashing or rejecting the connection outright, a 504 usually points at the upstream being alive but too slow, or a timeout value set too aggressively for a legitimately slow endpoint.

That site's own error_log, if you've configured a per-server-block log path, or the global error log if not. It'll usually show either a server block matching problem (wrong Host header routing to the wrong block, covered above), a permissions error reading a static file or SSL certificate, or a proxy_pass failure reaching that specific site's backend.

nginx -T (uppercase) is worth running alongside the log, since it dumps the full merged configuration nginx actually loaded, includes and all, which catches the surprisingly common case where an included config file isn't being picked up the way you assumed.

Medium questions

25

Each worker runs a single-threaded event loop built on an OS-level notification mechanism, epoll on Linux, kqueue on BSD/macOS, that lets one process watch thousands of sockets at once and only wake up work for the ones that actually have data ready. There's no thread per connection and no blocking wait on a socket that has nothing to say yet.

This is why a worker can hold open ten thousand idle keep-alive connections at almost no cost. The moment a task blocks on something the event loop can't make async, a slow disk read on an older config, for instance, that worker's entire event loop stalls until the blocking call returns, which is part of why nginx pushes so hard toward async-friendly primitives like sendfile and AIO.

The master process reads the new configuration file and checks its syntax before doing anything else. If it's invalid, the master logs the error and keeps the existing worker processes running on the old, known-good configuration, so a bad reload doesn't take the site down.

If it's valid, the master spawns brand new worker processes using the new config, and sends a graceful shutdown signal to the old workers. Old workers stop accepting new connections immediately but keep serving whatever requests they already had in flight until those finish, then exit. Net effect: config changes apply with zero dropped connections.

bash
nginx -t     # test config syntax without applying it
nginx -s reload  # or: kill -HUP <master_pid>

The rough ceiling is worker_processes × worker_connections. Set worker_processes 4 and worker_connections 1024, and you get a theoretical maximum of roughly 4096 simultaneous connections, though each proxied client connection typically consumes two connections from that pool, one to the client, one to the upstream, so the practical ceiling for reverse-proxy traffic is closer to half that number.

worker_processes auto matches the count to available CPU cores, which is the sane default for almost every deployment. Setting it manually only really makes sense when you're deliberately reserving cores for something else on the same box.

First it filters by the listen directive, matching the request's destination IP and port, with an explicit IP:port taking priority over a wildcard like 0.0.0.0:80. Among the server blocks that survive that filter, nginx compares the request's Host header against each block's server_name values.

The match order is exact name first, then longest matching leading wildcard (*.example.com), then longest matching trailing wildcard (www.*), then regex server names in the order they appear in the config, and finally whichever server block is marked default_server if nothing else matched.

nginx first checks for an exact match using location = /path, and if one exists, it wins immediately with no further searching. If there's no exact match, nginx scans every prefix location (plain location /path) and remembers whichever one has the longest matching prefix.

Only after that does it evaluate regex locations (location ~ for case-sensitive, location ~* for case-insensitive), in the order they're written in the file, and stops at the first one that matches. If a regex matches, it wins over the longest prefix match found earlier. If no regex matches, nginx falls back to that stored longest prefix.

nginx
location = /login {... }    # exact, checked first
location /api/ {... }      # prefix match
location ~*.(jpg|png)$ {... } # regex, checked after prefixes, in file order

For plain prefix locations, no. nginx evaluates all of them and picks whichever has the longest matching prefix, regardless of where it sits in the file. For regex locations, order absolutely matters, nginx tests them top to bottom and stops at the first match, so two regex locations that could both match the same URI will always resolve to whichever one appears first.

This asymmetry is worth stating out loud in an interview, because assuming file order controls everything (or that it controls nothing) are both wrong depending on which location type you're looking at.

Most likely a server_name using a broad wildcard, like *.example.com, or a regex server_name that's more permissive than intended, is matching before nginx ever falls through to a more specific block, or there's no dedicated default_server at all so requests are landing on whichever server block happens to be first in the config.

The fix is usually adding an explicit default_server that returns a 444 or a clear 404 for unrecognized hosts, plus tightening any wildcard server_name so it can't accidentally swallow subdomains you didn't intend to route there.

By default nginx forwards the request to the upstream using $proxy_host, the host and port from the proxy_pass URL, not the original Host header the client sent. If your backend generates absolute URLs, redirects, or does virtual-host routing based on the Host header, it'll see the internal upstream address instead of the public domain, breaking redirects and any host-based logic.

nginx
location / {
 proxy_pass http://127.0.0.1:8000;
 proxy_set_header Host $host;
}

X-Forwarded-For tells the backend the real client IP, since without it every request the backend sees would appear to originate from nginx's own address. $proxy_add_x_forwarded_for appends the current client's address to any existing X-Forwarded-For header instead of overwriting it, which matters when a request has already passed through one or more proxies upstream of this nginx instance, so the whole chain of hops stays visible rather than getting collapsed to just the last hop.

nginx
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;

proxy_connect_timeout caps how long nginx waits to establish the TCP connection to the upstream, default 60 seconds, and it can never exceed 75 seconds regardless of what you set. proxy_send_timeout caps the time allowed between two successive writes while nginx is sending the request body to the upstream. proxy_read_timeout caps the time allowed between two successive reads while nginx is waiting on the upstream's response.

A slow backend that hangs mid-response, rather than failing to connect at all, is a proxy_read_timeout problem, not a connect timeout problem, and tuning the wrong one is a common reason "I already raised the timeout" doesn't fix a 504.

With buffering on (the default), nginx reads the entire upstream response into memory (or spills to disk for very large responses) before sending anything to the client, which lets a slow client not hold a connection open to your backend for the whole transfer. With proxy_buffering off, nginx streams the response to the client as soon as it arrives from the upstream, synchronously, one small chunk at a time.

Turn it off for real-time or streaming responses, server-sent events, long-polling, or any endpoint where the client needs bytes as they're produced rather than after the whole response is ready. Leaving it on for those endpoints is the classic cause of an SSE stream that appears to "hang" and then dump everything at once.

least_conn sends the next request to whichever upstream server currently has the fewest active connections, rather than blindly cycling through the list. That matters when requests take meaningfully different amounts of time to finish, one backend instance handling a slow report-generation request shouldn't also get piled with new requests just because it's "next in line" under plain round robin.

nginx
upstream backend {
 least_conn;
 server srv1.example.com;
 server srv2.example.com;
}

ip_hash routes a given client IP to the same upstream server every time, based on a hash of the client's address, which gives you session persistence without a shared session store, useful when an app keeps session state in memory on a specific server.

The downside is uneven load and fragile failover behavior. Clients behind a shared corporate NAT or a mobile carrier's CGNAT can all hash to the same server, overloading it while others sit idle, and if that server goes down, every client hashed to it needs to be re-mapped, which can briefly dump a lot of previously-pinned traffic onto whatever server absorbs them next. Most teams solve session persistence with a shared cache like Redis instead and skip ip_hash entirely.

weight changes how large a share of requests a server gets relative to the others under round robin or least_conn, useful when your servers have different capacity. backup marks a server as standby, it receives zero traffic under normal conditions and only starts accepting requests once every non-backup server in the group is marked unavailable.

nginx
upstream backend {
 server srv1.example.com weight=3; # gets ~3x the traffic of an unweighted peer
 server srv2.example.com;
 server srv3.example.com backup;  # only used if srv1 and srv2 both fail
}

This is passive health checking, nginx doesn't proactively probe servers, it just watches real traffic. If a server fails to respond, or responds with an error nginx is configured to treat as a failure, max_fails times within a fail_timeout window (both default to reasonable values, 1 attempt and 10 seconds), nginx marks that server unavailable for the remainder of that same fail_timeout window and stops sending it new requests.

nginx
upstream backend {
 server srv1.example.com max_fails=3 fail_timeout=30s;
 server srv2.example.com max_fails=3 fail_timeout=30s;
}

After the window elapses, nginx tries that server again on the next request that would have gone to it, so it self-heals without any operator action, as long as the server actually recovered.

Declare a cache zone with proxy_cache_path at the http level, giving it a name and a size for the shared memory zone that holds cache keys and metadata, then reference that zone with proxy_cache inside the location you want cached.

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

location / {
 proxy_pass http://backend;
 proxy_cache mycache;
 proxy_cache_valid 200 10m;
}

levels=1:2 spreads cached files across a two-level directory structure so no single directory ends up with an unmanageable number of files, and keys_zone=mycache:10m sizes the in-memory index, roughly 8,000 keys per megabyte, separately from the actual cached response bodies on disk (nginx.org, ngx_http_proxy_module).

It's the string nginx hashes to decide whether two requests are "the same" for caching purposes. The default is roughly $scheme$proxy_host$request_uri, scheme, upstream, and full URI including query string.

You'd customize it when the default is either too broad or too narrow for your traffic. Too broad: a query string parameter that doesn't actually change the response, a marketing UTM tag, say, means every unique tag combination gets its own cache entry, fragmenting your cache and tanking hit rate for no reason. Too narrow: caching per-user content by mistake because the key doesn't account for a cookie or header that actually changes the response, which risks leaking one user's cached data to another.

It tells nginx which error conditions are allowed to serve an already-expired cached response instead of failing the request outright. If your backend goes down entirely, requests that would normally return a 502 or time out can instead get served the last known-good cached response, which is often a far better user experience than an error page.

nginx
proxy_cache_use_stale error timeout http_500 http_502 http_503;
proxy_cache_use_stale updating; # serve stale while a background refresh is in flight

proxy_cache_bypass tells nginx to skip reading from cache (fetch fresh from upstream) when any listed variable is non-empty or not "0". Pair it with proxy_no_cache so the fresh response also doesn't get written back into the cache for everyone else.

nginx
proxy_cache_bypass $cookie_session_id;
proxy_no_cache $cookie_session_id;

Since nginx 1.23.4 the default is TLSv1.2 TLSv1.3 (nginx.org, ngx_http_ssl_module). TLS 1.0 and 1.1 have known cryptographic weaknesses and are deprecated by every major browser and by PCI-DSS compliance requirements, so leaving them enabled buys essentially zero compatibility benefit at this point while keeping an attack surface open.

nginx
ssl_protocols TLSv1.2 TLSv1.3;

It caches TLS session parameters so a returning client can resume a session with an abbreviated handshake instead of doing a full asymmetric-crypto handshake from scratch, which is noticeably cheaper on both CPU and round trips. builtin caches per worker process, so a client's session cache hit depends on which worker happens to handle its next request, no guarantee at all with multiple workers. shared puts the cache in memory visible to every worker, so a session established through one worker can be resumed through any other.

nginx
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;

Historically it was a parameter tacked onto the listen directive, listen 443 ssl http2;. Newer nginx versions (1.25.1 and later) moved to a separate http2 on; directive instead, because the old syntax coupled HTTP/2 to a specific listen socket in a way that caused confusion when a server block had multiple listen directives.

nginx
server {
 listen 443 ssl;
 http2 on;
 ssl_certificate   /etc/ssl/example.com.crt;
 ssl_certificate_key /etc/ssl/example.com.key;
}

Either form still requires TLS in practice, since no major browser supports HTTP/2 over plaintext, so this directive almost always sits alongside an ssl listen block.

limit_req_zone defines a shared-memory zone keyed on some request attribute, usually the client IP, tracking request rate against it, and limit_req inside a location enforces that limit, optionally allowing a short burst above the steady rate.

nginx
limit_req_zone $binary_remote_addr zone=login:10m rate=5r/s;

location /login {
 limit_req zone=login burst=10 nodelay;
 proxy_pass http://backend;
}

burst lets a client temporarily exceed the steady rate up to that many queued requests, and nodelay serves burst requests immediately instead of artificially spacing them out, at the cost of allowing a sharper short-term spike through.

With sendfile on, the kernel copies a file's contents directly from the filesystem cache to the network socket, without that data ever being copied into nginx's own userspace buffer first. For serving static files, that's a real reduction in CPU and memory copies compared to the read-then-write path.

Turn it off in setups where nginx needs to inspect or modify the response body before sending it, some SSL configurations on older kernels had sendfile interactions worth checking, or where you're proxying rather than serving local files, in which case sendfile doesn't apply to that response at all anyway.

Check the error log for which timeout actually fired, connect, send, or read, since that tells you whether the backend never accepted the connection at all or accepted it and then took too long to respond. Then check whether the backend itself is the bottleneck (slow queries, exhausted worker pool, CPU saturation) versus nginx routing to a backend that's healthy but just slower than the configured timeout allows.

A quick, if blunt, mitigation is raising proxy_read_timeout to buy the backend more time, but that's a stopgap, not a fix, if the real problem is the backend degrading under load. Raising a timeout just means clients wait longer before getting the same eventual failure.

Hard questions

10

All workers share the same set of listening sockets, and older nginx versions used an optional accept_mutex to serialize which worker gets to call accept() next, avoiding the thundering-herd effect where every idle worker wakes up for one new connection. Modern Linux kernels with EPOLLEXCLUSIVE support let the kernel itself wake only one waiting worker for a given event, which is why accept_mutex defaults to off on current nginx, the OS now does the job the mutex used to do.

Either way, only one worker ends up owning a given accepted connection for its lifetime. There's no shared connection state between workers to corrupt, which is also why a single worker crashing doesn't take down connections that belong to other workers.

^~ tells nginx that if this prefix location ends up being the longest matching prefix, stop right there and skip regex evaluation entirely, even if a regex location further down the file would otherwise have matched the same URI.

nginx
location ^~ /static/ {
 # served directly, regex below never gets a chance to run
 root /var/www;
}
location ~*.php$ {
 # would normally handle.php files, but /static/app.php never reaches here
 fastcgi_pass 127.0.0.1:9000;
}

This is the exact gotcha that catches people debugging "why is my regex location being ignored." The answer is almost always a ^~ prefix location sitting earlier in the matching order and intercepting the request before the regex ever gets evaluated.

If proxy_pass includes a URI (anything after the host and port, even just a bare /), nginx replaces the matched part of the request path with that URI before forwarding. If proxy_pass has no URI at all, nginx forwards the full original request URI unchanged, location prefix included.

nginx
location /name/ {
 proxy_pass http://127.0.0.1/remote/;
}
# request for /name/test -> forwarded as /remote/test

location /name/ {
 proxy_pass http://127.0.0.1;
}
# request for /name/test -> forwarded as /name/test, prefix kept

Get this wrong, and a request like /v1/test against proxy_pass http://backend/api; (no trailing slash on either side) turns into http://backend/apiv1/test, string concatenation, not path joining. The documented fix is to keep the trailing slash consistent on both the location and the proxy_pass URI, or omit it from both (nginx.org, ngx_http_proxy_module). In our mock interview logs at LastRoundAI, this is one of the few nginx questions where candidates with real DevOps experience visibly relax, because they've clearly been burned by it once already and remember the fix cold.

Passive checks, the max_fails/fail_timeout mechanism available in open source nginx, only find out a server is down by watching real client requests fail against it, meaning some number of real users hit an error before nginx reacts. Active health checks proactively send synthetic probe requests to each upstream on an interval, independent of real traffic, and pull a server out of rotation before any live request ever reaches it.

Active health checks with a dedicated health_check directive are an NGINX Plus feature, not something open source nginx ships natively. Teams on open source nginx that want proactive health checking typically bolt it on externally, an orchestrator's own liveness probe removing an unhealthy pod from the upstream list, or a sidecar script that rewrites the upstream config and reloads nginx when a server fails a check.

First, the error log, nginx logs the specific reason for a 502, connection refused, upstream sent an invalid or truncated header, or upstream prematurely closed the connection, and each of those points somewhere different. Connection refused usually means the backend's own connection pool or process count is saturated. Invalid header often means the backend crashed mid-response or a proxy buffer size is too small for a legitimately large response header.

Second, worth checking whether proxy_next_upstream is configured sensibly, since by default nginx will retry a failed request against the next upstream server automatically for certain error types, and if that retry also fails, the 502 the client actually sees may be masking a first attempt that failed for a completely different reason than the one in the final log line.

A stampede happens when a popular cached response expires and a burst of concurrent requests for that same URL all arrive before any of them has repopulated the cache, so every single one gets forwarded to the upstream simultaneously instead of just one. For a hot endpoint, that can turn a single cache expiry into a self-inflicted spike against your backend.

nginx
proxy_cache_lock on;
proxy_cache_lock_timeout 5s; # give up waiting and pass through if the fill takes too long

With proxy_cache_lock on, only the first request for a given cache key is actually allowed to reach the upstream while the entry is being populated. Every other concurrent request for that same key waits for the fill to complete and then gets served from cache, instead of all of them hammering the backend at once.

It sets the maximum number of open file descriptors each worker process is allowed, which matters because every open connection, client and upstream both, consumes a file descriptor, along with every open log file and cached file handle. Under high concurrency, the OS-level default (often 1024) gets exhausted well before worker_connections does, and new connections start failing with "too many open files" even though nginx's own config looks fine.

nginx
worker_rlimit_nofile 65535;
events {
 worker_connections 16384;
}

Raising it in the nginx config alone isn't always enough, the OS-level hard limit (via ulimit or a systemd unit's LimitNOFILE) has to be raised too, or nginx's requested value gets silently capped at whatever the OS allows.

nginx pre-builds a hash table for server names to make Host header lookups fast, and that table has a fixed bucket size, sized to fit the longest server_name currently configured, with some headroom. Add enough virtual hosts, or one with a long name, and the table can no longer fit within the configured bucket size, so nginx refuses to start rather than silently building a broken hash table.

nginx
http {
 server_names_hash_bucket_size 64; # bump from the default, commonly 32 on many builds
}

The error message itself usually tells you the exact size to try next, nginx suggests doubling the bucket size as the standard fix, and it's a config-level tuning knob, not a sign anything is actually broken with the virtual hosts themselves.

The old worker stops accepting brand new connections the moment it receives the shutdown signal, but it doesn't forcibly kill connections that are already open. A websocket or long-poll connection already established on that worker keeps running normally until it naturally closes, the client disconnects, the application ends the stream, or a configured timeout finally forces it, at which point that specific old worker process exits.

In practice this means a reload during heavy websocket usage can leave old workers alive far longer than you'd expect, sometimes indefinitely if a connection never closes on its own, which is worth knowing before assuming a reload always fully completes within seconds.

nginx picks which certificate to present based on the SNI (Server Name Indication) hostname the client sends during the TLS handshake, before nginx even sees the HTTP Host header. If a client, or an old TLS-terminating proxy sitting in front of it, doesn't send SNI at all, nginx has no way to know which of several server blocks on that IP the client actually wants, and falls back to whichever server block is effectively first or marked default for that listen socket, presenting that certificate regardless of which site the client meant to reach.

This is the standard reason for those cert-mismatch warnings that only affect some clients, usually older devices or software that predates widespread SNI support, while every modern browser hits the correct certificate without issue.

How to prepare for a nginx interview

Skip re-reading a directive reference top to bottom. Stand up a small reverse proxy locally, two toy backends behind one nginx instance, with real load balancing, a proxy_cache zone, and SSL termination, and deliberately break something: misplace a trailing slash on proxy_pass, drop the Host header, set fail_timeout absurdly low. Watching what actually happens teaches the failure modes far faster than memorizing the directive names.

Across the DevOps and backend mock interviews run through LastRoundAI, the proxy_pass trailing-slash question and the location matching order question trip up roughly the same share of candidates, both are things people configure correctly by copying an example without fully internalizing why it works. We don't track an exact percentage split between the two, but in review they show up as close calls almost every time.

One more thing worth knowing walking in: HTTP/2's directive moved from a listen-line flag to its own http2 on; setting in nginx 1.25.1. It's a small syntax change, but it's exactly the kind of "did you keep up with the last year or two of releases" detail an interviewer who runs nginx day to day will notice if your config examples are stuck on the old syntax.

Get the reps in before the real thing

LastRoundAI's mock interview mode runs live infrastructure and systems-design rounds with follow-up questions that probe past the first correct answer, the free plan includes 15 credits a month that reset monthly. Starter is $19/mo if a handful of sessions isn't enough runway to feel ready.

Once your answers hold up under a follow-up, Auto-Apply queues tailored applications to DevOps, SRE, and backend roles for your review, 10 a month on the free plan, up to 400 a month on the Ultimate plan, and nothing goes out until you approve it.

Questions about either product go to contact@lastroundai.com. That's the only inbox we check.

How this list was built

Worth being straight about where these questions come from, because plenty of pages in this category are not. The set was compiled from a research pass across official documentation, vendor release notes, published engineering writing and public discussion of hiring processes, then cross-checked against the current version of each technology so nothing here describes behaviour that has since changed.

What that means in practice: these are the questions the material supports as reasonable and current for this role, not a transcript of any one company's loop. We have not sat in on your interview and we are not going to claim we have. Treat the list as well-sourced preparation rather than a leaked question bank, and expect your panel to phrase things their own way.

If you spot something out of date, tell us at contact@lastroundai.com and we will fix it.

Frequently asked questions

What Nginx topics come up most often?

Interviewers concentrate on the parts that cause production incidents rather than the parts that are pleasant to learn. Expect the fundamentals to be assumed and the follow-up questions to sit one layer below what a tutorial covers.

Do I need hands-on Nginx experience to pass?

It shows quickly either way. Textbook answers hold up until the interviewer asks what you did when it broke, and that is usually the question that separates candidates. A small real project you can discuss honestly beats a longer list of familiarity claims.

Is Nginx still worth learning in 2026?

For interview purposes the question is really whether the teams you are targeting use it, which is worth checking against their actual job postings rather than general popularity rankings. Where it is in use it tends to be deeply embedded and slow to replace.

Should I memorise Nginx syntax for the interview?

Rarely worth it. Most interviewers care that you know what to reach for and why, and will not fail you for forgetting an exact flag. Being confidently wrong about behaviour costs far more than admitting you would check the documentation.

What is the most common mistake in Nginx interviews?

Answering the question that was asked and stopping there. The strongest candidates add the trade-off or the failure mode without being prompted, which is what signals real use rather than revision.

Leave a Reply

Your email address will not be published. Required fields are marked *