The Apache MPM Decision: Why event Usually Wins and prefork Still Has a Job
Apache's MPM choice—prefork, worker, or event—determines memory use and concurrency. Learn when to switch to event MPM with PHP-FPM and how to validate the change safely.
30 Sept 2025, 14:24 UTC

The problem: idle processes are eating your RAM
Your Apache server feels sluggish under load, ps shows hundreds of httpd processes, and memory use climbs even when traffic is modest. Before scaling hardware, check which Multi-Processing Module (MPM) Apache loaded at startup. The MPM controls how the server maps incoming connections to OS processes and threads, and on most Unix builds it is fixed at compile time or selected by a distro package. Picking the wrong one is an architectural mistake that tuning alone cannot fix.
The practical takeaway: for modern thread-safe stacks, the event MPM is the better default. prefork is a compatibility tool, not a performance one.
What an MPM actually decides
Apache's MPM is a pluggable engine that binds network sockets to execution contexts. Only one MPM runs at a time. The three you will encounter are:
- prefork: launches a single-process, single-thread child for every connection. It is the most compatible model because nothing shares memory, but each idle connection still costs a full process—often 10–30 MB of resident memory.
- worker: starts a fixed number of child processes, each with a pool of threads. Memory drops because threads share the parent's address space, but every loaded module must be thread-safe.
- event: same threaded model as
worker, plus a dedicated listener thread per process that handles keep-alive connections in the background. This frees worker threads to serve actual requests.
Since Apache 2.4, event has been the default on many platforms, yet plenty of production systems still run prefork because of legacy module dependencies.
Why keep-alive changes the math
HTTP keep-alive lets a client reuse a TCP connection for multiple requests. Under prefork and worker, an idle keep-alive connection still occupies a process or worker thread until it times out. Mobile networks and slow clients make this worse: hundreds of "connected but idle" sessions can exhaust your worker pool while actual request throughput stays low.
The event MPM solves this by handing keep-alive sockets to a listener thread that waits for the next request without consuming a worker thread. It relies on APR support for efficient event notification interfaces such as epoll on Linux, kqueue on BSD, or IOCP on Windows. The result is better concurrency for the same memory budget, especially when many connections are slow or mostly idle.
Worked example: switching from prefork to event with PHP-FPM
This example assumes Apache 2.4 on Debian or Ubuntu, where MPMs are toggled with a2enmod/a2dismod. It also assumes you are moving PHP handling from mod_php (not thread-safe) to PHP-FPM via mod_proxy_fcgi. Run all commands as root or with sudo.
1. Confirm the current MPM. Any user with apachectl in PATH can run:
apachectl -V | grep -i mpm
Expected output includes a line such as Server MPM: prefork. If you see event, you are already on the threaded path.
2. Verify PHP-FPM is available. Run php-fpm -v to confirm the daemon is installed. Apache will proxy .php requests to it over FastCGI.
3. Swap the MPM and enable the proxy module.
a2dismod mpm_prefork
a2enmod mpm_event
a2enmod proxy_fcgi
4. Update the virtual host. Remove any mod_php handlers and add a proxy rule. Inside the relevant <VirtualHost> block:
ProxyPassMatch ^/(.*\.php)$ fcgi://localhost:9000/var/www/$1
Adjust the socket and document root to match your PHP-FPM pool configuration.
5. Validate and restart.
apachectl configtest
systemctl restart apache2
configtest must return Syntax OK before you restart. After restart, re-run apachectl -V | grep -i mpm to confirm the active MPM changed.
Rollback: if the site fails, disable event and re-enable prefork:
a2dismod mpm_event
a2enmod mpm_prefork
systemctl restart apache2
If you removed mod_php packages, reinstall them before restarting, or PHP pages will download as plain text.
Trade-offs and hard limits
event is not a free upgrade. The biggest constraint is thread safety: every module loaded into the server must be safe to run in a threaded process. mod_php is explicitly not thread-safe, and forcing it under worker or event risks crashes or data corruption. If you depend on a legacy third-party module that is not thread-safe, prefork remains the safer choice despite its memory cost.
Even with event, MaxRequestWorkers acts as a ceiling on total concurrent threads across all child processes. It must stay below both available RAM and the file-descriptor limit. A conservative check is:
ulimit -n
If the limit is lower than MaxRequestWorkers plus your other sockets, Apache will log server reached MaxRequestWorkers setting and drop connections. Also remember that mod_cgi still forks a process per request, so CGI-heavy sites gain little from threaded MPMs.
Some load balancers and legacy clients mishandle long-lived keep-alive connections. Test under realistic traffic—wrk -t4 -c100 -d30s http://localhost/ is a quick way to compare behavior—and watch mod_status with ExtendedStatus On to see idle versus busy workers.
Where to start
Run apachectl -V | grep -i mpm today. If the answer is prefork, audit every loaded module for thread safety. If the stack is modern, move to event with PHP-FPM and tune MaxRequestWorkers against your actual RAM and ulimit -n. If you are locked into non-thread-safe modules, stay on prefork, but right-size StartServers and MaxRequestWorkers so idle processes do not consume the budget meant for real traffic.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.