skip to content

Config Model and Contexts

Nginx config is nested contexts with directives that inherit downward, which is why a setting placed in the wrong block silently does nothing. Interviewers ask about inheritance and includes, because that is how config bugs actually present themselves.

on this pageshow

questions

5

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%

answer

  1. braces create scope, not indentation
  2. outer level supplies the default
  3. innermost definition wins
  4. events for workers, http for serving
  5. location only inside server

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.

solid answer

~50 s

nginx config is nested blocks, not a flat list. **main** is the top of the file, outside every brace: process-level settings such as `user`, `worker_processes`, `pid` and `error_log`. **events** configures connection processing — `worker_connections` lives only there. **http** holds everything about serving HTTP and is where site-wide defaults such as `keepalive_timeout`, `gzip`, `log_format` and `client_max_body_size` belong, along with `upstream` and `map` blocks. **server** is one virtual host inside `http`, carrying `listen` and `server_name`. **location** is a URI-matching block inside a `server`. Scoping is by nesting: a directive applies to its own block and everything nested below it, and an inner block inherits the outer value unless it declares that directive itself, in which case the inner value wins for that block. Putting a directive in a context where it is not allowed is a startup error; putting it in a legal but wrong context usually just does nothing visible.

code

nginx · 22 lines
nginx
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;

events {
    worker_connections 1024;
}

http {
    include       /etc/nginx/mime.types;
    keepalive_timeout 65;
    client_max_body_size 1m;

    server {
        listen 80;
        server_name example.com;

        location /uploads/ {
            client_max_body_size 50m;
        }
    }
}

go deeper

for a junior

Be able to name the five core contexts and say which settings live in each, and state plainly that an inner block inherits the outer value unless it sets its own.

for a middle

Explain the difference between simple and block directives, why braces rather than indentation define scope, and why a directive in a legal-but-wrong context fails silently instead of erroring.

for a senior

Show how you use inheritance deliberately: defaults declared once in http, overrides only where a host genuinely differs, and nginx -T to prove which block a value really came from.

for a principal

Own the convention: decide what belongs at http level for the whole fleet versus what a team may override per vhost, and treat repeated per-server settings as configuration debt to be pulled upward.

## Contexts are nested scopes An nginx configuration file is a tree of blocks called *contexts*. Each directive is declared by its module as valid in a specific set of contexts, and its effect is scoped to the block it sits in plus everything nested inside that block. Reading a config therefore means reading inwards, not top to bottom. The core contexts, from the outside in: - **main** — the top level of the file, outside all braces. Process-wide settings live here: `user`, `worker_processes`, `pid`, `error_log`, `worker_rlimit_nofile`, and top-level `include` statements. - **events** — a block that configures how connections are accepted and processed: `worker_connections`, `use`, `multi_accept`. - **http** — everything about serving HTTP. This is the natural home for defaults that should apply to every site: `sendfile`, `keepalive_timeout`, `gzip`, `log_format`, `access_log`, `client_max_body_size`, plus the `upstream` and `map` blocks. - **server** — a single virtual host, nested inside `http`, carrying `listen`, `server_name` and any default that applies to that whole host. - **location** — a URI-matching block nested inside a `server` (or inside another `location`), carrying per-path behaviour. Alongside these there are siblings: `stream` sits at main level next to `http` for TCP/UDP proxying, `mail` for mail proxying, and `types`, `if` and `limit_except` appear nested further down. The rule is uniform: a block is a scope. ## Simple directives and block directives A **simple directive** is a name, its arguments, and a semicolon: ```nginx keepalive_timeout 65; ``` A **block directive** takes the same form but ends in braces instead of a semicolon. When a block contains further directives, it is a context: ```nginx http { keepalive_timeout 65; server { listen 80; server_name example.com; location /api/ { client_max_body_size 20m; } } } ``` The braces are the only thing that creates scope. Indentation is a convention for humans; nginx ignores it entirely, which is why a misplaced closing brace can silently move a whole `location` into the wrong `server`. ## Inheritance runs downward only In the example above, the `server` and the `location` both operate with `keepalive_timeout 65`, because neither redefines it. The `location` uses `client_max_body_size 20m`, while every other path in that server uses whatever `http` set (or nginx's compiled-in default of 1m if nothing did). Three consequences follow: 1. **Inheritance is one-way.** Nothing a `location` sets can affect its `server`, and nothing a `server` sets can affect `http`. A directive placed too deep simply does not apply to the requests that never reach that block. 2. **The nearest definition wins.** For ordinary single-value directives, the innermost level that declares the directive determines the value; the outer value is not merged with it, it is simply not consulted. 3. **Defaults belong high.** Anything you want everywhere should be declared once in `http` rather than copy-pasted into every `server`, because every nested level picks it up for free. That is the practical payoff of the model, and it is why config bloat is usually a sign someone did not trust inheritance. A small but important exception: a family of directives that accumulate a list — `add_header` and `proxy_set_header` are the well-known ones — are inherited only if the current level defines *none* of them. Defining one at an inner level discards the whole inherited list rather than adding to it. ## Two very different failure modes Putting a directive somewhere it is not permitted is loud: `nginx -t` reports `"worker_connections" directive is not allowed here` and nginx refuses to start or reload. Putting a directive somewhere legal but ineffective is silent: `client_max_body_size` in a `location` that the request never matches, or a `proxy_read_timeout` on the wrong virtual host, produces no error at all and no behaviour change. Almost every "I changed the config and nothing happened" report is this second case. ## Reading the config you actually have Because real deployments split the tree across `include` files, the file you edited is rarely the whole picture. `nginx -T` validates the configuration and prints the entire resolved tree, includes expanded, so you can see which block a directive really landed in and whether a deeper block overrides it.

  • If the same simple directive appears twice inside one context, what does nginx do?
    For most directives nginx refuses the config with a `"..." directive is duplicate` error at parse time, so a copy-paste mistake in the same block fails fast. A smaller set is defined to accumulate within a level — `add_header`, `proxy_set_header`, `listen` and `error_page` among them — and there repeating the directive adds another entry rather than being an error.
  • Where does the `stream` context fit, and why can't you put a `location` in it?
    `stream` is a top-level context alongside `http`, provided by the stream module, for proxying raw TCP and UDP. It has its own `server` blocks with `listen` and `proxy_pass`, but no `location`, because at that layer nginx never parses a request — there is no URI to match on, only a connection to forward.
  • Why is it better to set a common default in `http` than to repeat it in every `server` block?
    Every nested `server` and `location` inherits it, so there is exactly one place to change it and no chance of one vhost drifting. Repetition also hides intent: a reader cannot tell whether a repeated value is deliberate for that host or leftover copy-paste. Override in a `server` only where that host genuinely differs.

Contexts work like nested CSS rules: a value set on the outer selector applies to everything inside it until a more specific inner rule states its own value.

saying these in an interview costs you the question

  • Says nginx reads the config top to bottom like a script
  • Puts worker_processes or worker_connections inside the http block
  • Thinks a location block can sit directly in http
  • Believes indentation determines which block a directive belongs to
  • Assumes outer and inner values are merged for every directive

context

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

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

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 inherit an nginx deployment where 60 virtual hosts are hand-edited into a single 3,000-line nginx.conf. How would you restructure that configuration, and how would you make changing it safe?

level: principalimportance: nice to knowfreq 28%

basics

~20 s

Split it into one file per virtual host pulled in by include, lift genuinely shared settings once into the http context so inheritance does the repetition, factor recurring fragments into snippets, and gate every change on a rendered nginx -t in CI with a scripted reload.

open as a page