← All Tools

Why Nginx Handles Massive Concurrency So Well

Guide · Last verified Aug 28, 2026

You've probably heard "Nginx is faster than Apache" a hundred times, but few people can explain exactly where and by how much. The accurate statement isn't "always faster" — it's "overwhelmingly better under heavy concurrency." That gap doesn't come from raw CPU power; it comes from a fundamentally different way each server handles a single connection. This guide puts numbers behind that architectural difference.

1. Apache's traditional model: one execution unit per connection

Apache's default MPM (Multi-Processing Module) options — prefork and worker — assign a process or thread to every incoming client connection. Even while a connection sits idle waiting, that process or thread stays alive, holding onto memory. If 10,000 concurrent connections come in, in theory 10,000 execution units have to exist at once, which means 10,000 candidates for context switching. With each process typically using a few MB of memory, server memory drains linearly as concurrency climbs.

2. Nginx's event loop: it processes events, not connections

Nginx takes the opposite approach. A small number of worker processes (usually matching the CPU core count) each run a single event loop, using non-blocking OS kernel I/O notification mechanisms like epoll (Linux) or kqueue (BSD) to watch thousands of connections simultaneously. Instead of assigning a thread to each connection, it only handles work when an event fires — "this socket has data to read" — and immediately moves to the next event. Idle connections are effectively monitored by the kernel itself, costing the worker process almost no memory or CPU. The result: on identical hardware, Nginx can sustain far more concurrent connections on far less memory than Apache prefork. This architectural difference is the core reason Nginx is so widely used as a reverse proxy, static file server, and load balancer.

The memory difference, conceptually: assume Apache prefork averages 8MB per process — handling 10,000 concurrent connections then works out to roughly 80GB of memory. Nginx handles the same connections with just 4 worker processes (on a 4-core CPU) running event loops, with per-connection memory overhead in the low kilobytes, so the same load fits into a few hundred MB. Real-world numbers vary with content type and Keep-Alive settings, but this is the order-of-magnitude gap between the two models' "fixed cost per connection."

3. worker_processes and worker_connections: the real levers of the event model

The two directives that actually control Nginx's event model are worker_processes, set at the top of nginx.conf (the main context), and worker_connections, inside the events {} block. Setting worker_processes auto; spins up one worker per CPU core, and worker_connections sets the maximum number of connections a single worker can hold open at once (default: 1024). So the theoretical maximum concurrent connections this server can handle is worker_processes × worker_connections. With 4 cores and worker_connections at 4096, that's a theoretical ceiling of 16,384 simultaneous connections (though you also need to raise the OS's file descriptor limit — ulimit — to actually open that many).

These two directives live in the main/events context, which applies to the server as a whole — so they're not part of the server-block output produced by the Nginx config generator, which assembles per-domain SSL, reverse proxy, and gzip settings. To tune the event model itself, you have to edit the top of nginx.conf directly, separate from any generated server block. It's common to look for worker_connections inside a server block and come up empty, not realizing this distinction.

4. So why is Apache still used at all?

Nginx wins on raw concurrency handling, but Apache's exact tradeoff — a dedicated process or thread per request — is precisely what makes per-directory dynamic configuration via .htaccess and in-process module extensions like mod_php so flexible. In practice, the common combination is Nginx in front for static files and reverse proxying, with Apache handling legacy PHP applications or shared hosting environments. You can compare the configuration syntax for both directly with the Apache config generator and the .htaccess generator.

5. A practical check: is your server actually benefiting from the event model?

Putting Nginx in front of your stack doesn't automatically make it fast. If worker_connections is left at its default (1024), if your Keep-Alive timeout is too long and idle connections hog resources, or if gzip and static caching are turned off, you're not getting the full benefit of the event loop. The HTTP header checker is the quickest way to confirm your response headers and cache policy are actually being applied, and the website speed estimator is the quickest way to check overall response speed.

Frequently Asked Questions

Q. Is Nginx always faster than Apache?

A. No. Raw single-request processing speed is often about the same, or varies by content type. Where Nginx clearly wins is how little memory and CPU it burns while holding up under a large number of concurrent connections.

Q. Is it always better to set worker_processes as high as possible?

A. No. More workers than you have CPU cores just adds context-switching overhead. The official recommendation is auto (matching your core count) — going beyond your core count doesn't scale performance proportionally.

Q. Does the Nginx config generator's output include event-model settings?

A. No. The generator only assembles per-domain server blocks (SSL, proxy, gzip, headers, caching). worker_processes and worker_connections belong to the server-wide main/events context at the top of nginx.conf, so they need to be edited separately by hand.

Q. What are epoll and kqueue?

A. Both are OS kernel I/O event notification mechanisms. epoll is used on Linux, kqueue on BSD-family systems (including macOS), and Nginx uses them to monitor thousands of sockets for state changes with very low overhead.