skip to content

Apache httpd ships three main multi-processing modules — mpm_prefork, mpm_worker and mpm_event. How does each one map connections to processes and threads, and what specifically does mpm_event change?

level: middleimportance: must knowfreq 75%

answer

  1. one connection, one process
  2. threads shrink the per-connection cost
  3. what happens between two keep-alive requests
  4. a listener thread holds idle sockets
  5. still one thread per in-flight request

basics

~20 s

mpm_prefork dedicates one single-threaded process per connection; mpm_worker runs many threads inside a few processes; mpm_event is worker plus a listener thread that holds idle keep-alive connections so no worker thread is parked on them.

solid answer

~50 s

In Apache httpd the MPM owns how a connection becomes a unit of execution. `mpm_prefork` forks a pool of single-threaded children and gives each connection an entire process — maximum isolation, no thread-safety requirement, and the most memory per concurrent connection. `mpm_worker` is a hybrid: a few children, each running `ThreadsPerChild` threads, so a concurrent request costs a thread stack rather than a whole process, but every loaded module and library must be thread-safe. `mpm_event` keeps worker's threading and adds a listener thread in each child that holds connections sitting idle in keep-alive, so a worker thread is claimed only while a request is actually being processed and handed back as soon as the response is written. That is the whole point: under prefork and worker an idle persistent connection occupies a worker for the whole `KeepAliveTimeout`; under event it does not. Event does not make request handling asynchronous — a handler blocked on a database still holds its thread.

go deeper

for a junior

Be able to say that prefork gives each connection its own process while worker and event use threads, and that this is why prefork uses far more memory at the same concurrency.

for a middle

Explain the mechanics: children and threads per child, and precisely what the event MPM's listener thread does with a connection that is idle between two keep-alive requests.

for a senior

Show you can tell an idle-connection problem from a slow-request problem before recommending an MPM change, and state what each MPM demands of the modules you load.

for a principal

Own the trade as a platform choice: isolation and non-thread-safe module support versus memory per connection, and be clear about which class of production problem changing the MPM cannot solve.

## What an MPM is Apache httpd deliberately separates *how connections are accepted and dispatched* from *what happens to a request*. The multi-processing module owns the first half: the listening sockets, the pool of children, and which process or thread ends up running the request through the module chain. In httpd 2.4 the MPM is a loadable module and exactly one may be active; `httpd -V` (or `apachectl -V`) prints it as `Server MPM:`. ```apacheconf LoadModule mpm_event_module modules/mod_mpm_event.so ``` ## mpm_prefork: one process per connection The parent forks children ahead of demand (`StartServers`) and keeps a spare pool between `MinSpareServers` and `MaxSpareServers`, up to `ServerLimit` processes. Each child is single-threaded and serves exactly one connection at a time, so the number of simultaneously served connections equals the number of children, capped by `MaxRequestWorkers`. What you buy: hard isolation. A segfault in a handler kills one child and one connection. Nothing in the address space is shared with another request, so libraries that are not thread-safe are perfectly safe here. What you pay: a full process per concurrent connection. Without an embedded interpreter a child may be a few MB resident; with an in-process language module it can be tens of MB, and that number multiplies by your concurrency ceiling. ## mpm_worker: threads inside processes Worker runs a smaller set of children, each with `ThreadsPerChild` request-handling threads, so the theoretical concurrency is `ServerLimit × ThreadsPerChild` and the effective ceiling is `MaxRequestWorkers`. Spare capacity is now measured in threads (`MinSpareThreads`, `MaxSpareThreads`). A concurrent request costs a thread stack (`ThreadStackSize`) plus per-request pools rather than an entire process image, which is a large reduction in memory per connection. The costs are the mirror image of prefork's benefits. Every module, and every library those modules link, must be thread-safe. A crash takes down all threads in that child, not just one connection. Keeping several children rather than one is partly a blast-radius decision. ## The keep-alive problem, and what event fixes Under prefork and worker, the worker that accepts a connection stays bound to it for the connection's whole lifetime — including the idle gaps between requests on a persistent connection. With `KeepAliveTimeout 5`, a browser that sends one request and then thinks for five seconds keeps that worker doing nothing for five seconds, while it still counts against `MaxRequestWorkers`. On a keep-alive-heavy site, most of your workers can be idle-but-occupied. The historical workaround was to set `KeepAliveTimeout` very low, which throws away connection reuse. `mpm_event` keeps worker's process/thread structure and adds a dedicated listener thread per child. Connections that are idle in keep-alive (and connections in lingering close) are handed to that listener, which polls them; a worker thread is claimed only when a request is actually readable and is released as soon as the response is written. So concurrency in *connections* decouples from concurrency in *requests being processed*, and you can keep a generous `KeepAliveTimeout` without paying a worker for it. The number of connections a child will hold beyond its busy workers is not unlimited — `AsyncRequestWorkerFactor` bounds it. ## What event does not change Request handling is still synchronous and still one thread per in-flight request. If your handler blocks for 400 ms on a database call, that thread is unavailable for 400 ms exactly as it would be under worker. Event fixes *idle connections*, not *slow requests*, and it is not an event loop in the sense that an asynchronous server is — the async part is confined to connection management. Because event inherits worker's threading, it inherits the thread-safety requirement too. That is precisely why an in-process language module built without thread safety pins a deployment to prefork and denies it both worker's memory savings and event's keep-alive handling. ## Choosing between them Event is the default for httpd 2.4 builds and the right answer for ordinary HTTP workloads: static content, reverse proxying, and any application reached over a protocol rather than embedded in the server. Worker is mainly of historical interest now — event is worker plus a strict improvement for keep-alive, with the same requirements. Prefork survives for exactly one reason: a module in your stack is not safe to run in a threaded child, and you would rather pay memory than debug data races in someone else's C code. In an interview, the sentence that shows you understand the family is: prefork trades memory for isolation, worker trades isolation for memory, and event trades nothing — it removes the cost of *waiting* connections while leaving the cost of *working* threads exactly where it was.

  • If mpm_event is strictly better than mpm_worker, why does mpm_worker still ship?
    Mostly history and conservatism. Event is worker plus asynchronous handling of idle keep-alive connections, and it carries the same thread-safety requirements, so there is rarely a technical reason to choose worker on a modern build. Worker remains for platforms or module stacks where the event MPM's polling behaviour has caused trouble, and as a fallback that is easy to reason about.
  • Does switching to mpm_event help a server whose handlers spend most of their time waiting on a slow backend?
    Barely. Event frees workers from idle keep-alive connections, not from in-flight requests. A thread blocked on a slow upstream is occupied under every MPM. You would see a benefit only if a large share of your worker occupancy was connections waiting between requests — check whether workers are in keep-alive state or actually processing before expecting a win.
  • Why does the choice of MPM affect which modules you can load?
    Prefork children are single-threaded, so a module or library with global mutable state is safe there. Worker and event run many threads in one address space, so every loaded module must be thread-safe. Loading a non-thread-safe module under a threaded MPM produces intermittent corruption and crashes rather than a clean startup error, which is why prefork persists.

saying these in an interview costs you the question

  • Says mpm_event makes Apache fully asynchronous like an event-loop server
  • Claims prefork is faster because processes are cheaper than threads
  • Thinks event MPM frees a worker blocked on a slow database call
  • Believes worker and event have no thread-safety requirements
  • Says the MPM is chosen at compile time in httpd 2.4

context