When troubleshooting Nginx performance problems or abnormal exits, the first step is usually figuring out what its internal processes are actually doing. It helps to picture Nginx’s process structure as a restaurant: the manager (master) never cooks — they handle scheduling, menu review, and hiring and firing; several chefs in the back kitchen (worker) each mind their own stove, and every dish a customer orders comes from their hands; plus two part-time storeroom keepers who only show up when caching is enabled. The formal definition of Nginx is: a multi-process model of one master process plus a set of worker processes — the master manages and schedules, the workers take on all client requests.

Three Kinds of Processes, Three Sets of Duties

The master process is the one and only manager. It reads and validates the configuration, binds the ports, spins up and monitors the workers, and handles signals — in day-to-day operations like nginx -s reload and nginx -s stop, it is always the master that receives the signal. It never touches a single client request itself; it just makes sure “the people doing the work” are on duty.

The worker processes take on all requests. Each worker runs its own event loop independently, managing hundreds to thousands of connections; the worker count defaults to matching the CPU core count — one worker per core, none preempting another. It is the workers that actually process requests, proxy traffic, and return responses. The event loop’s multiplexer is visible in the startup log — on Linux it prints using the "epoll" event method, on macOS/BSD it’s kqueue; a worker relies on it to watch every connection at once.

cache manager and cache loader are the helper processes of the caching era. You only see them when proxy caching is configured: the loader reads the cache index from disk into shared memory at startup, and the manager periodically checks cache entries and cleans out expired objects. Without caching configured, neither process exists — they appear on demand.

Actual testing shows it most directly. Pin a container to 4 cores with --cpuset-cpus=0-3, run an nginx:alpine inside, and ps aux makes everything clear at a glance:

Multi-process model, measured

The three segments of output in this figure correspond to three key behaviors of the multi-process model: the worker count strictly equals the core count; kill one worker and the master spins up a replacement within a second (PID 30 disappears, 43 appears) to restore full staffing; during nginx -s reload the master’s PID doesn’t move an inch (always 1) while the workers swap out as a whole generation (30-33 → 48-51) — old workers exit after finishing their in-flight requests, new workers take over with the new configuration, and service is never interrupted.

The Tension It Resolves

A process structure has to satisfy two goals that pull against each other: concurrency must squeeze the full value out of multiple cores, yet a single point of failure must not take everybody down.

A single-process structure fails on both counts: every task is crammed into one process, multi-core hardware goes unused, and when one stage jams under heavy load the entire line queues up behind it. Its upside is simplicity and low memory footprint — fine for small tools, not for a web server. The multi-threaded model has strong concurrency, but threads share the same memory — one thread’s memory leak pollutes the whole process, and one thread crashing often takes the rest down with it.

Nginx’s multi-process design takes a third path through this tension: memory is completely isolated between workers — a crashed worker loses only the connections it held, and the master immediately fills the gap; a dozen-plus workers spread across ten cores, with not one core of concurrency wasted.

Single Process Multi-Threaded Nginx Multi-Process
Multi-core utilization Poor Good Good
Fault isolation None Weak (shared memory) Strong (independent processes)
Crash recovery Service ends Whole-process restart Master backfills in seconds
Memory overhead Lowest Medium One copy per process, slightly higher

Applicability Boundaries

Multi-process is not an unconditional best answer — three boundaries to know:

  • Memory overhead: each worker holds its own copy of memory; open too many processes and memory usage climbs linearly — worker_processes auto to match the core count is all you need; blindly raising it brings no extra throughput.
  • Shared data must go through shared memory: the flip side of worker isolation is that workers share no state; cross-worker data like rate limiting (limit_req_zone) and the cache index is implemented by Nginx with shared-memory segments — which is also the precondition for cache manager’s existence.
  • Inside the process it’s still a single-threaded event loop: blocking calls are forbidden inside a worker; if a third-party module blocks, it freezes every connection on that entire worker — the root cause of “one worker jams, and every request on that worker jams with it.”

Summary

Back to the opening restaurant: the master is the manager who never cooks — config, signals, and backfilling; the workers are the chefs behind every request, their count matched to the core count; the two storeroom keepers clock in only when caching is on. Next time you troubleshoot, start with ps aux to tell master from worker, kill a worker to watch automatic backfill, and reload to watch the smooth generational swap — those two actions are also the routine health check for whether the process model is healthy.

This article was rebuilt from the author’s CSDN blog posts published between 2020 and 2024, originally published on CSDN.