Prefork vs Worker MPM: Apache's Process Model Trade-offs
Apache HTTP Server has powered production web infrastructure for decades, and at its core sits a fundamental architectural choice: the multi-processing module (MPM). The two most commonly deployed MPMs—prefork and worker—represent different concurrency strategies, each with distinct performance characteristics and operational trade-offs that remain relevant even in an era of async-everything architectures.
Prefork: One Process Per Connection
The prefork MPM uses a traditional process isolation model. Apache spawns multiple child processes at startup, and each process handles exactly one connection at a time. When a request arrives, the parent process assigns it to an idle child. If all children are busy, the parent spawns additional processes up to a configured limit.
This model offers strong isolation. A crash in one child process—caused by a buggy module, memory corruption, or misbehaving script—affects only that single request. The process dies, the parent notices, and a new child is spawned to replace it. No other connections are disrupted.
Prefork is also the only safe choice for non-thread-safe libraries. Many legacy PHP extensions, some database drivers, and older third-party modules were written without thread safety in mind. Running them in a multi-threaded environment risks race conditions, memory corruption, and subtle data leaks between requests. Prefork sidesteps the problem entirely by giving each request its own isolated memory space.
The cost is resource overhead. Each process carries its own heap, stack, and a full copy of loaded modules. A server handling 200 concurrent connections needs 200 separate processes, each consuming several megabytes of memory. Context switching between processes is relatively expensive, and spawning new processes under load introduces latency.
Worker: Threads Within Processes
The worker MPM takes a hybrid approach. It spawns multiple processes, but each process runs several threads. Connections are distributed across threads within those processes, allowing a single process to handle dozens of simultaneous requests.
This scales much better. Threads share the same memory space, so the per-connection overhead drops dramatically. Creating and destroying threads is faster than forking processes, and context switches between threads in the same process are cheaper than inter-process switches. A server that would need 200 processes under prefork might handle the same load with 10 processes running 20 threads each.
The shared memory model also enables more efficient caching. Module state, compiled regex patterns, and application-level data structures can live in process memory and be accessed by all threads without serialization overhead.
But shared memory introduces risk. A segmentation fault or null pointer dereference in any thread crashes the entire process, taking down all the connections that process was handling. Thread-unsafe code can corrupt shared state in ways that are hard to reproduce and debug. A memory leak in one thread eventually exhausts the process, affecting every connection it serves.
Choosing for Production
The decision often comes down to workload characteristics and runtime safety guarantees. If you’re running mod_php or any module that explicitly documents thread-safety limitations, prefork is the safe default. The memory overhead is real, but the isolation prevents entire classes of failure.
For high-concurrency workloads with thread-safe modules—reverse proxying, static file serving, or applications using FastCGI or mod_wsgi in daemon mode—worker’s efficiency advantage becomes significant. The reduced memory footprint allows higher connection density per server, and the faster thread overhead improves responsiveness under load.
Modern deployments often bypass the question entirely by putting Apache behind a reverse proxy like nginx or using event-driven application servers. But many production environments still rely on Apache’s flexibility, and the MPM choice remains a first-order tuning decision.
The Event MPM and Beyond
Apache 2.4 introduced the event MPM, which extends worker with dedicated listener threads that handle keep-alive connections without blocking worker threads. This addresses one of worker’s key inefficiencies: threads sitting idle while waiting for the next request on a persistent connection.
Event represents the best of both worlds for thread-safe workloads, but it still inherits worker’s requirement that all modules be thread-safe. For codebases that can’t meet that requirement, prefork remains the fallback.
The architectural divide between prefork and worker reflects a broader tension in server design: isolation versus efficiency, safety versus scale. Understanding the trade-offs lets you tune Apache to match your application’s needs rather than accepting default settings that may not fit your workload.