How to Configure Nginx Worker Processes for Maximum Throughput

A practical guide to configuring Nginx worker processes, connections, and event handling for maximum throughput — with real config examples and benchmarking tips.

Your Nginx configuration file ships with sensible defaults. But "sensible defaults" and "maximum throughput" are two very different things. If you're running a busy web server and haven't touched your worker process settings, you're almost certainly leaving performance on the table.

This guide walks through the key directives that control how Nginx handles concurrent connections — and how to tune them for your specific hardware and workload. We'll cover the numbers, the reasoning behind them, and the tradeoffs you need to understand before making changes.

Why Worker Process Configuration Matters for Nginx Performance Tuning

Nginx uses an event-driven, asynchronous architecture. Unlike Apache's process-per-request model, Nginx spawns a small number of worker processes, each capable of handling thousands of simultaneous connections. This is what makes it so efficient under load.

But that efficiency depends on your configuration matching your hardware. Set too few workers and you waste CPU cores. Set too many and you introduce context-switching overhead that actually slows things down. The goal is to find the sweet spot for your server.

If you're curious how server-level configuration choices ripple into real-world metrics, we covered the relationship in detail in Why Your Time to First Byte Is Costing You Conversions (And How to Fix It).

Setting the Right Number of Worker Processes

The worker_processes directive in nginx.conf controls how many worker processes Nginx spawns. The most common recommendation is to match this to the number of CPU cores available on your server.

worker_processes auto;

Using auto tells Nginx to detect the number of CPU cores and set the value accordingly. This is the right choice for most setups. If you want to set it manually, first check your core count:

nproc --all

Then set it explicitly:

worker_processes 4; # for a 4-core server

One important nuance: if your server is dedicated entirely to Nginx, matching cores to workers is ideal. If you're running other CPU-intensive processes alongside Nginx (like PHP-FPM, MySQL, or Redis), you may want to leave a core or two free to avoid resource contention.

Nginx Performance Tuning: Worker Connections and the Math Behind Them

The worker_connections directive sets the maximum number of simultaneous connections each worker process can handle. It lives inside the events block:

events { worker_connections 1024; }

The total maximum connections your server can handle is:

max_connections = worker_processes × worker_connections

So a 4-core server with worker_connections 1024 can theoretically handle 4,096 simultaneous connections. In practice, you'll hit OS-level file descriptor limits before you hit this ceiling — which brings us to the next setting.

Raising the File Descriptor Limit

Each connection consumes a file descriptor. By default, Linux limits this to 1,024 per process. If your worker_connections exceeds that, you'll hit errors under load.

Set the limit in nginx.conf with:

worker_rlimit_nofile 65535;

And update your system-level limits in /etc/security/limits.conf:

nginx soft nofile 65535 nginx hard nofile 65535

Also update /etc/sysctl.conf to raise the system-wide file descriptor limit:

fs.file-max = 200000

Apply the change with sysctl -p. These three changes work together — missing any one of them can still cause connection failures under high load.

CPU Affinity: Pinning Workers to Cores

By default, the OS scheduler decides which CPU core runs each Nginx worker. This is usually fine, but under very high traffic you can reduce cache misses and context-switching by pinning each worker to a specific core using worker_cpu_affinity.

# For a 4-core server with 4 workers worker_cpu_affinity 0001 0010 0100 1000;

Each bitmask corresponds to a CPU core. The first worker is pinned to core 0, the second to core 1, and so on. For an 8-core server with 8 workers:

worker_cpu_affinity auto;

The auto value (available in Nginx 1.9.10+) handles the pinning automatically. This is the cleaner option if you're on a modern Nginx version.

CPU affinity tuning tends to show the most benefit on servers handling 10,000+ requests per second. Below that threshold, the gains are usually negligible.

The Multi-Accept and Use Directives

Two more settings inside the events block are worth knowing about:

events { worker_connections 4096; multi_accept on; use epoll; }

multi_accept on tells each worker to accept as many new connections as possible in a single pass, rather than one at a time. This reduces latency under burst traffic. It's almost always worth enabling.

use epoll explicitly sets the connection processing method to epoll, which is the most efficient option on Linux. Nginx usually selects this automatically, but being explicit doesn't hurt and makes your config self-documenting.

Keepalive Connections and Worker Throughput

Keepalive connections let a single TCP connection serve multiple HTTP requests. This reduces the overhead of connection setup and teardown — which matters a lot at scale.

http { keepalive_timeout 65; keepalive_requests 100; }

keepalive_timeout sets how long an idle connection stays open. keepalive_requests limits how many requests a single connection can serve before it's closed.

For high-traffic sites, increasing keepalive_requests to 500 or even 1000 can meaningfully reduce connection overhead. Just be aware that very long keepalive timeouts can tie up worker connections with idle clients — so don't set keepalive_timeout too high (65 seconds is already generous for most use cases).

A Complete Nginx Performance Tuning Example

Here's a consolidated configuration block that applies everything covered above, suitable for a dedicated 4-core web server:

worker_processes auto; worker_rlimit_nofile 65535; events { worker_connections 4096; multi_accept on; use epoll; } http { keepalive_timeout 65; keepalive_requests 500; sendfile on; tcp_nopush on; tcp_nodelay on; # ... rest of your http config }

The sendfile, tcp_nopush, and tcp_nodelay directives are worth including too. sendfile lets the kernel transfer files directly without copying data through user space. tcp_nopush batches response headers and the beginning of the file into a single packet. tcp_nodelay disables Nagle's algorithm for faster delivery of small packets. Together, they reduce latency for static file serving.

How to Verify Your Changes Are Working

After updating your config, always test before reloading:

nginx -t

Then reload without dropping connections:

nginx -s reload

To benchmark the impact, use wrk or ab (Apache Bench) to simulate concurrent load:

# 12 threads, 400 concurrent connections, 30-second test wrk -t12 -c400 -d30s https://yourdomain.com/

Compare requests per second and latency percentiles before and after your changes. A well-tuned server should show noticeably lower p99 latency under sustained load.

For a broader look at what else affects your server's speed scores, Core Web Vitals and Hosting: Why Your Server Is Either Helping or Hurting Your Scores is a good companion read.

When Nginx Tuning Alone Isn't Enough

Worker process configuration gets you a long way, but it's one layer of a larger performance stack. If your application is slow — slow database queries, unoptimized PHP, no object caching — Nginx will faithfully deliver slow responses faster. That's not the same as a fast site.

Pairing good Nginx configuration with server-side caching (like Redis object caching) and a well-tuned application layer is where the real gains compound. We run Redis-backed object caching on all our managed servers by default, which means database query results are served from memory rather than re-executed on every request — a significant multiplier on top of any Nginx-level tuning you do.

For a deeper look at caching strategies that complement your Nginx setup, see How to Set Up Redis Caching on Your Server Without Breaking Anything.

The Takeaway

Nginx performance tuning at the worker level isn't complicated, but it does require understanding what each directive actually does. The short version: match worker_processes to your CPU cores, raise your file descriptor limits to match your worker_connections, enable multi_accept, and use epoll. Then benchmark to confirm the changes are having the effect you expect.

Small configuration changes at this level can translate to measurable improvements in throughput and latency — especially under sustained concurrent load. Start with auto where available, measure, and adjust from there.

For more on the full performance optimization picture, our performance overview covers how server-level tuning fits into the broader stack.