Nginx Fundamentals

Master and worker process architecture

graph TD master["Master process<br/>(reads config, manages workers, no client traffic)"] master --> w1["Worker 1<br/>(event loop, epoll)"] master --> w2["Worker 2<br/>(event loop, epoll)"] master --> w3["Worker N (= CPU cores)"] w1 --> c1["thousands of connections"] w2 --> c2["thousands of connections"]

The master process never touches client traffic — it reads the configuration, binds the listening sockets, and spawns/manages worker processes (and reloads config without dropping connections, on nginx -s reload). Each worker is a single-threaded event loop built on the same epoll mechanism already covered — one worker handles thousands of concurrent connections without one OS thread per connection, for exactly the reasons that page explains: a blocked/idle connection costs a registered fd, not a parked thread.

Why worker count should match CPU cores, not exceed it

Because each worker is single-threaded and CPU-bound for the actual request-processing work (parsing headers, running config logic, TLS handshakes), running more workers than CPU cores doesn't add capacity the way adding more threads would for a blocking-I/O server — the workers would just contend for the same cores. worker_processes auto; (the standard modern default) sets it to the detected core count for exactly this reason.

Request processing, at a high level

graph TD conn["Connection accepted by a worker"] --> parse["Parse request headers"] parse --> serverblock["Match server block<br/>(by Host header + listening port)"] serverblock --> location["Match location block<br/>(see Configuration and Security)"] location --> handler["Execute handler:<br/>serve static file, or proxy_pass, etc."] handler --> resp["Send response"]

A single worker interleaves handling many connections at different stages of this pipeline simultaneously — while one connection is waiting on a slow upstream response (proxy_pass's backend), the worker is free to make progress on other connections' requests in the meantime, the same non-blocking pattern as any epoll-based event loop.

Common pitfall

Assuming a slow request from one client blocks other clients on the same worker, and trying to "fix" it by adding more worker processes beyond the CPU core count, treats a fundamentally I/O-bound situation (waiting on a slow upstream, waiting on a slow disk read) as if it were CPU contention. Nginx's event loop already handles many concurrent waiting connections cheaply — the actual fix for a slow backend is addressing the backend's latency (or adding a timeout) rather than adding worker processes that would just compete for the same limited CPU cores without solving the underlying wait.