The Event-Driven Core, Revisited
Nginx In Depth
Chapter 2 · The Event-Driven Core, Revisited
Web Servers Fundamentals Chapter 2 described Nginx's event-driven model conceptually: a small number of worker processes, each an event loop handling many connections without a thread per connection. This chapter tunes that model for a real workload — the two directives that actually control it, the OS-level mechanism underneath, and the real ceiling those directives run into.
worker_processes
worker_processes sets how many worker processes Nginx runs. Setting it to auto matches it to the number of available CPU cores — generally the right starting point, since each worker's own event loop runs most efficiently with its own dedicated core. Setting it too low under-uses available hardware; setting it meaningfully higher than the core count doesn't add real capacity and just adds context-switching overhead between more processes than the machine can genuinely run in parallel.
worker_connections
worker_connections, set inside the events {} block, caps how many simultaneous connections a single worker can hold open. The real theoretical maximum number of simultaneous connections Nginx can handle is worker_processes × worker_connections — but this number is also capped by the operating system's own file descriptor limit, since every open connection consumes one file descriptor. Raising worker_connections without also raising the OS limit (worker_rlimit_nofile, and the OS's own ulimit) simply hits that ceiling instead of the number configured in Nginx.
The Event Loop in Practice
On Linux, Nginx's event loop is built on epoll, a kernel mechanism that lets a single process ask "which of these thousands of connections actually has new data ready?" without checking each one individually — the technical foundation underneath Web Servers Fundamentals' own conceptual description of "non-blocking I/O." This is what lets one worker process track thousands of mostly-idle connections cheaply, rather than needing a dedicated thread watching each one.
multi_accept and accept_mutex
Two lesser-known but real tuning directives, both inside events {}: multi_accept controls whether a worker accepts every waiting new connection in one pass or just one per event-loop cycle; accept_mutex historically serialized which worker was allowed to accept a new incoming connection, to avoid all workers waking up for the same single new connection (a "thundering herd"). Modern Nginx defaults accept_mutex to off, since modern kernels already handle this efficiently on their own — a good example of a once-important tuning knob that's since become mostly unnecessary.
| Directive | Default | Controls |
|---|---|---|
worker_processes | auto (recent versions) | How many worker processes run |
worker_connections | 512 | Max simultaneous connections per worker |
worker_rlimit_nofile | Unset (inherits OS limit) | Max open file descriptors per worker |
accept_mutex | off | Whether workers serialize accepting new connections |
worker_processes/worker_connections upward to use all available hardware makes sense. Inside a Kubernetes deployment (per this course's own Ingress-controller connection from Chapter 1), a single Nginx instance is often deliberately kept small and constrained — scaling out with more Pods running more Nginx instances, rather than scaling one instance up vertically, is frequently the preferred approach in that environment.
worker_connections to some very large number guarantees more capacity. Each held-open connection consumes real memory and one real file descriptor — a number set far beyond what the machine's own RAM and OS file-descriptor limit (ulimit -n) can actually support does nothing useful; connections simply fail once that real ceiling is hit, regardless of what the config file says. The actual capacity ceiling is worker_processes × worker_connections, capped by whatever the operating system genuinely allows — not an arbitrarily large number typed into a config file.
Hands-On Exercises
A server has 4 CPU cores and worker_connections set to 1024. Calculate the theoretical maximum number of simultaneous connections this configuration allows, and name the one other real-world limit that could still cap this number lower.
📄 View solutionA team sets worker_connections to 100000 on a small VPS with a default OS file-descriptor limit of 1024 per process, expecting this to significantly raise their server's capacity. Using this chapter's own warning box, explain what will actually happen.
📄 View solutionExplain, in your own words, why epoll allows a single Nginx worker to efficiently track thousands of mostly-idle connections, rather than needing a dedicated thread to check each connection individually.
📄 View solutionChapter 2 Quick Reference
- worker_processes — usually
auto, matching CPU core count - worker_connections — max simultaneous connections per worker; theoretical total capacity is
worker_processes×worker_connections - Real capacity is also capped by the OS's own file descriptor limit (
worker_rlimit_nofile/ulimit) — not just the config file's own numbers - epoll is the Linux kernel mechanism underneath Nginx's own non-blocking event loop
- accept_mutex — off by default on modern kernels, a once-important knob that's now mostly unnecessary