Choosing an Apache httpd 2.4 MPM: event vs worker vs prefork
Apache 2.4's MPM choice controls concurrency, HTTP/2 support, and how you must run PHP. Compare event, worker, and prefork, then migrate to event + PHP-FPM and validate it.
10 Aug 2026, 19:04 UTC

If you run Apache httpd 2.4 and you have never consciously picked a Multi-Processing Module (MPM), you are probably running whatever your distro shipped — and that choice determines how many concurrent connections you can serve, whether HTTP/2 works at all, and how you must deploy PHP. The short version: for almost every modern workload, mpm_event with PHP-FPM is the right answer. mpm_prefork survives only for non-thread-safe embedded modules, and mpm_worker is mostly a fallback when event misbehaves with a specific module.
The decision and its constraints
The MPM is the process/threading model httpd uses to accept and serve connections. Only one MPM can be loaded at a time. Your constraints, in rough order of importance:
- PHP: classic
mod_phpembeds the PHP interpreter in httpd, and many PHP extensions historically were not thread-safe, forcing prefork. The modern supported pattern is PHP-FPM (a separate FastCGI process pool), which removes that constraint entirely. - HTTP/2:
mod_http2requires a threaded MPM. Under prefork, HTTP/2 is silently unavailable and clients fall back to HTTP/1.1. - TLS and keep-alive concurrency: modern browsers hold many idle keep-alive connections open. How cheaply your MPM parks those idle connections drives your capacity ceiling.
- Legacy modules: some third-party modules assume a process-per-request model and are unsafe under threads.
Comparing the three MPMs
| prefork | worker | event | |
|---|---|---|---|
| Model | One process per connection, no threads | Processes × threads; a thread is held for the whole connection | Threaded, plus a listener thread that hands idle keep-alive connections back so worker threads are freed |
| Idle keep-alive cost | One whole process | One whole thread | Parked asynchronously; near-zero worker cost |
| Memory per connection | Highest | Medium | Lowest |
| mod_php compatible | Yes | Risky / not recommended | Risky / not recommended |
| HTTP/2 (mod_http2) | No | Yes | Yes |
| Key tunables | ServerLimit, MaxRequestWorkers (process counts) | ServerLimit, ThreadsPerChild, MaxRequestWorkers | Same as worker, plus AsyncRequestWorkerFactor |
| Best fit | Non-thread-safe embedded modules only | Threaded workloads where event causes issues | Default for typical web/TLS/HTTP-2 traffic |
Note on naming: MaxRequestWorkers was called MaxClients in Apache 2.2. Check your exact version before copying tuning snippets from older documentation.
Trade-offs that actually matter
Event is the default recommendation because its dedicated listener thread parks idle keep-alive and TLS connections in async slots instead of pinning a worker thread. Under browser-heavy traffic — many connections, most of them idle at any instant — this is the difference between needing 400 threads and needing 40. The trade-off is slightly more internal complexity and a different capacity-planning shape: you tune threads plus AsyncRequestWorkerFactor, not just process counts.
Worker is the same threaded model without the async parking. It is a reasonable fallback if a module misbehaves under event, but it holds a thread per connection for the connection's lifetime, so keep-alive-heavy traffic wastes threads.
Prefork trades memory for isolation: a crashing or misbehaving request only takes down its own process. That isolation is exactly why non-thread-safe code (old mod_php builds, some legacy modules) requires it. The cost is brutal under concurrency: every idle keep-alive connection holds an entire process, and you lose HTTP/2.
One non-obvious risk when moving from prefork to event: threaded MPMs use far less memory per connection, but they behave differently under slow-client (Slowloris-style) load. Event mitigates idle-connection exhaustion for keep-alive, but slow request bodies still occupy workers — revisit Timeout and consider mod_reqtimeout after switching.
Concrete implementation: prefork + mod_php to event + PHP-FPM
This is the most common real-world migration. Steps below are for Debian/Ubuntu (a2enmod tooling); on RHEL-family systems you edit the MPM LoadModule line in /etc/httpd/conf.modules.d/00-mpm.conf instead. Run all commands as root or via sudo.
# 1. Install PHP-FPM (adjust version to your deployment)
apt install php-fpm
# 2. Disable mod_php and the prefork MPM, enable event + proxy_fcgi
a2dismod php8.2 mpm_prefork
a2enmod mpm_event proxy_fcgi setenvif
a2enconf php8.2-fpm
# 3. Restart both services
systemctl restart php8.2-fpm apache2
Risks: switching MPM restarts all connections, so do it in a maintenance window. Before switching, audit apachectl -M output for third-party modules that might assume prefork. If you skip step 2's a2dismod php*, Apache may fail to start because mod_php refuses to load under a threaded MPM.
Validating the switch
First confirm the active MPM — this reads the running configuration, not just installed packages:
apachectl -V | grep 'Server MPM'
# Expected: Server MPM: event
Then verify behavior under load. Enable mod_status with ExtendedStatus On, restricted to localhost or your admin network — an open status page leaks client IPs and request URLs:
<Location /server-status>
SetHandler server-status
Require ip 127.0.0.1
</Location>
Generate keep-alive-heavy traffic from another machine (ab -k -c 200 -n 20000 http://yourhost/ or a wrk equivalent) and watch the scoreboard. Under event, you should see idle keep-alive connections parked (shown as C/closing or async slots) rather than each holding a W (sending) or busy worker. If you use HTTP/2, confirm negotiation with curl --http2 -I https://yourhost/ and check for HTTP/2 200. Finally, confirm PHP requests route through FPM: a phpinfo() page should show Server API: FPM/FastCGI, not Apache 2.0 Handler.
Limitations
Directive defaults and module names vary across distro packages and minor 2.4 releases; verify against your installed version's docs before tuning. If you depend on a module that is not thread-safe and has no FastCGI-style alternative, prefork may genuinely be your only option — accept the HTTP/1.1-only limitation or put a reverse proxy in front. This guide assumes Apache httpd 2.4 on Unix; Windows uses a different MPM entirely (mpm_winnt).
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.