yum upgrades for production use, this is the repository for you.
Active subscription is required.
The worker_processes directive is one of the first things you’ll encounter in an NGINX configuration, yet it’s often misunderstood. Setting it incorrectly can leave your server underutilizing available CPU cores—or worse, thrashing due to excessive context switching.
Most tutorials simply tell you to use worker_processes auto; and move on. But what does auto actually do? When should you deviate from it? Let’s dive deep into NGINX’s source code to understand what’s really happening.
What is worker_processes?
NGINX uses an event-driven, non-blocking architecture. The master process manages configuration and worker lifecycle, while worker processes handle the actual client connections. Each worker runs an independent event loop (using epoll on Linux, kqueue on BSD/macOS) and can handle thousands of concurrent connections.
The worker_processes directive tells NGINX how many of these worker processes to spawn:
worker_processes 4;
The “auto” Value: What Happens Under the Hood
When you specify worker_processes auto;, NGINX determines the number of workers based on detected CPU cores. Looking at the source code in nginx.c:
if (ngx_strcmp(value[1].data, "auto") == 0) {
ccf->worker_processes = ngx_ncpu;
return NGX_CONF_OK;
}
The ngx_ncpu variable is populated at startup using platform-specific system calls:
- Linux:
sysconf(_SC_NPROCESSORS_ONLN)— returns online CPUs - macOS:
sysctlbyname("hw.ncpu", ...) - FreeBSD:
sysctlbyname("hw.ncpu", ...)with optional hyperthreading adjustment viamachdep.hlt_logical_cpus
Key insight: On Linux, auto returns the number of online processors, which includes hyperthreads. A 4-core CPU with hyperthreading will report 8, and worker_processes auto; will spawn 8 workers.
Verify Your System’s CPU Count
Check what NGINX will see as “auto”:
# Linux - what nginx uses internally
getconf _NPROCESSORS_ONLN
# Alternative: show physical vs logical CPUs
lscpu | grep -E '^CPU(s)|Thread|Core'
# macOS
sysctl -n hw.ncpu
When “auto” Isn’t Optimal
While auto works well in most scenarios, there are cases where manual tuning is necessary:
1. Shared/VPS Hosting with CPU Limits
Cloud providers often limit CPU usage via cgroups. Your VPS might see 8 CPUs but only be allowed to use 2 cores worth of CPU time. Running 8 workers creates unnecessary overhead.
# Check if CPU limits exist (Linux with cgroups v2)
cat /sys/fs/cgroup/cpu.max
In this case, set workers to your actual CPU quota:
worker_processes 2;
2. CPU-Intensive Backends
If NGINX is primarily a reverse proxy fronting CPU-intensive applications (PHP-FPM, Python, Ruby), you may want fewer workers to leave CPU headroom for the backend:
# On an 8-core server with heavy PHP-FPM workload
worker_processes 4;
3. High-Memory Modules
Some NGINX modules (like ModSecurity, Lua, or heavy caching) consume significant memory per worker. If memory is constrained, reduce workers:
# Check per-worker memory usage
ps -C nginx -o pid,rss,cmd --sort=-rss
4. Single-CPU Systems or Containers
For minimal containers or single-CPU instances, auto correctly returns 1. However, explicitly setting it documents intent:
worker_processes 1;
Hard Limit: Maximum 1024 Processes
NGINX has a compile-time hard limit defined in ngx_process.h:
#define NGX_MAX_PROCESSES 1024
This limit covers all NGINX processes (master, workers, cache manager, cache loader). In practice, you’ll never approach this limit—systems would run out of resources long before.
Related Directive: worker_cpu_affinity
By default, the OS scheduler decides which CPU runs each worker. The worker_cpu_affinity directive pins workers to specific CPUs, reducing cache misses and context-switch overhead.
Automatic Affinity (Recommended)
Since NGINX 1.9.10, you can use auto for automatic round-robin CPU assignment:
worker_processes auto;
worker_cpu_affinity auto;
Under the hood, NGINX’s ngx_get_cpu_affinity() function distributes workers across available CPUs:
// Round-robin assign workers to CPUs
for (i = 0, j = n; ; i++) {
if (CPU_ISSET(i % CPU_SETSIZE, mask) && j-- == 0) {
break;
}
}
CPU_SET(i % CPU_SETSIZE, &result);
On Linux, this ultimately calls sched_setaffinity() to bind each worker.
Manual Affinity with Bitmasks
For fine-grained control, specify CPU bitmasks per worker:
# 4 workers pinned to CPUs 0-3 (binary: 0001, 0010, 0100, 1000)
worker_processes 4;
worker_cpu_affinity 0001 0010 0100 1000;
# Alternative hex notation for many CPUs
worker_cpu_affinity 0x1 0x2 0x4 0x8;
NUMA-Aware Configuration
On NUMA systems, binding workers to CPUs on the same NUMA node as their memory improves performance:
# Check NUMA topology
numactl --hardware
lscpu | grep NUMA
# 2 workers per NUMA node (example: 2 nodes, 4 cores each)
worker_processes 8;
worker_cpu_affinity 00001111 11110000;
Related Directive: worker_connections
The total concurrent connections NGINX can handle is:
max_connections = worker_processes × worker_connections
The default worker_connections is 512. For busy servers:
events {
worker_connections 4096;
}
Ensure your system’s file descriptor limit supports this:
# Check current limit
ulimit -n
# Set in nginx.conf
worker_rlimit_nofile 65535;
Practical Recommendations
| Scenario | Recommendation |
|---|---|
| Dedicated server, primarily serving static files | worker_processes auto; |
| Dedicated server, reverse proxy only | worker_processes auto; |
| VPS with CPU limits (cgroups) | Set to your actual CPU quota |
| Server with memory-heavy modules | Reduce workers, monitor memory |
| Shared server running NGINX + heavy backends | Half of available CPUs for NGINX |
| Container/single-CPU | worker_processes 1; |
How to Verify Worker Count
After configuring, verify NGINX spawned the expected workers:
# Count worker processes
ps aux | grep 'nginx: worker' | grep -v grep | wc -l
# See all nginx processes with CPU affinity
ps -eo pid,psr,comm | grep nginx
To see which CPU each worker is bound to (requires worker_cpu_affinity):
# Linux: check affinity of each worker
for pid in $(pgrep -f 'nginx: worker'); do
taskset -p $pid
done
Summary
worker_processes auto;works correctly for most dedicated servers—it spawns one worker per detected CPU (including hyperthreads)- On Linux, NGINX uses
sysconf(_SC_NPROCESSORS_ONLN)to detect CPUs - Pair with
worker_cpu_affinity auto;to pin workers to CPUs and reduce context switching - Deviate from
autowhen: using VPS with CPU limits, running memory-heavy modules, or sharing CPU with intensive backends - NGINX has a hard limit of 1024 total processes, but resource constraints will limit you long before that
For most configurations, this is all you need:
worker_processes auto;
worker_cpu_affinity auto;
events {
worker_connections 4096;
}
