Dropping mod_php: Why Apache's Event MPM Plus PHP-FPM Is Worth the Migration
mod_php forces every Apache connection to carry a full PHP interpreter. Switching to the Event MPM with PHP-FPM decouples them — here's why it matters, how to wire it up, and what breaks.
16 Jul 2026, 23:26 UTC

If your Apache server still runs PHP through mod_php, every connection to your site — including a browser sitting idle on a keep-alive connection or a request for a static image — is being held by a process that carries a full embedded PHP interpreter. That's the real cost of the prefork MPM plus mod_php pairing, and it's why moving to the Event MPM with PHP-FPM is one of the highest-leverage changes you can make on a busy LAMP server.
The thesis is simple: mod_php forces Apache's process model to be shaped around PHP, and PHP doesn't need that. Decoupling them lets each piece do what it's good at.
Why prefork and mod_php are stuck together
mod_php embeds the PHP interpreter directly inside each Apache process. Historically, PHP builds weren't reliably thread-safe, so Apache had to use the prefork MPM — one process per connection, no threads — to keep interpreters isolated from each other. That coupling has a nasty side effect: a process heavyweight enough to execute PHP is also the thing serving your CSS files and holding open idle keep-alive connections. Under many concurrent or slow clients, memory usage climbs fast because each connection pins a full process.
What the Event MPM changes
The Event MPM uses a small number of processes, each with many worker threads, plus dedicated listener threads. The key trick is that idle keep-alive connections are handed back to the listener instead of occupying a worker. A worker thread only gets involved when there's an actual request to process. The result: hundreds of mostly-idle connections no longer translate into hundreds of processes. Event is the default MPM on most modern distributions — but don't assume. Some packages still auto-select prefork when mod_php is installed, so verify:
# Run as any user; reads the compiled-in config\napachectl -V | grep -i mpm\n# Expected line: Server MPM: eventOn RHEL-family systems the binary may be httpd instead of apachectl. If you see prefork, you're on the legacy model.
Wiring Apache to PHP-FPM
Since Event is threaded, PHP can no longer live in-process. PHP-FPM (FastCGI Process Manager) runs PHP as a separate service with its own pools of worker processes, and Apache proxies PHP requests to it over a Unix socket or TCP using mod_proxy_fcgi. A minimal Debian/Ubuntu migration looks like this (run as root or via sudo):
# Disable the old pairing, enable the new one\nsudo a2dismod php8.2 mpm_prefork\nsudo a2enmod mpm_event proxy_fcgi setenvif\nsudo systemctl restart apache2Then, inside each virtual host that serves PHP, point requests at the FPM socket (adjust the socket path to your PHP version):
<FilesMatch \\.php$>\n SetHandler \"proxy:unix:/run/php/php8.2-fpm.sock|fcgi://localhost\"\n</FilesMatch>On RHEL-family systems the equivalent module loading lives in /etc/httpd/conf.modules.d/ rather than a2enmod/a2dismod. After restarting, confirm the change end to end: load a page containing phpinfo() and check that Server API reads FPM/FastCGI, not Apache 2.0 Handler.
The isolation bonus
Once PHP is out of Apache, each FPM pool can run as a different Unix user with its own memory limits, process counts (pm = dynamic or ondemand in the pool config), and restart behavior. You can restart or resize one site's PHP capacity without touching Apache or any other site — something mod_php could never give you.
The trade-offs you should plan for
The biggest workflow break: php_value and php_flag directives in .htaccess or Apache config stop working, because Apache no longer controls the interpreter. Those settings must move into FPM pool definitions (php_admin_value[...]) or per-directory .user.ini files. Before migrating, grep your docroots for php_value and test any setting-dependent pages afterward.
Two more cautions. First, audit your loaded Apache modules — anything third-party that isn't thread-safe blocks Event MPM adoption. Second, sizing matters: a very long KeepAliveTimeout or an oversized FPM pool can quietly erase the memory savings. Verify the win with a load test (ab or wrk with concurrent keep-alive connections) and compare resident memory before and after.
Where to start
Pick one low-traffic virtual host, migrate it to FPM, confirm the Server API via phpinfo(), and watch memory under realistic concurrency for a day. If the numbers hold — and on a keep-alive-heavy site they usually do — roll the pattern out to the rest. You'll end up with a web server that scales with connections and a PHP layer that scales with actual PHP work, which is how the two should have been separated all along.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.