Websites & apps

Run PHP-FPM with Apache using proxy_fcgi and mpm_event

Move Apache from mod_php and the prefork MPM to PHP-FPM with mpm_event and proxy_fcgi. Use less memory, serve more visitors and pick a PHP version per virtual host.

IntermediateLinux server4 min read

The classic Apache setup loads PHP inside every Apache process with mod_php, which forces the prefork MPM. Each process then carries the full weight of PHP, even when it only sends a CSS file. Running PHP in a separate PHP-FPM service lets Apache use the lighter event MPM and hand only PHP requests to FPM. This guide makes that switch on Ubuntu, Debian, AlmaLinux and Rocky Linux.

Before you start

  • Apache serving at least one PHP site through a virtual host.
  • PHP-FPM installed for the version you want, for example php8.3-fpm on Ubuntu. To run several versions side by side, see Install multiple PHP versions on Ubuntu and Debian.
  • A maintenance window of a few minutes. Apache restarts during the switch.
  • A copy of your config:
Bash
  sudo cp -a /etc/apache2 ~/apache2-backup   # Ubuntu and Debian
  sudo cp -a /etc/httpd ~/httpd-backup       # AlmaLinux and Rocky

Switch MPM and modules on Ubuntu and Debian

Check which MPM and PHP module are active:

Bash
apache2ctl -M | grep -E "mpm|php"

If you see mpm_prefork_module and php8.3_module, disable them and enable the event MPM and the FastCGI proxy. Replace 8.3 with your version:

Bash
sudo apt install -y php8.3-fpm
sudo a2dismod php8.3 mpm_prefork
sudo a2enmod mpm_event proxy_fcgi setenvif

The php8.3-fpm package ships a ready-made Apache snippet. Enabling it routes every .php file on the server to that FPM version:

Bash
sudo a2enconf php8.3-fpm

Test and restart (a reload is not enough when changing MPM):

Bash
sudo apachectl configtest
sudo systemctl restart apache2

Warning: If configtest fails after disabling mod_php, do not restart. A restart with a broken config takes every site down. Fix the reported line, or restore your backup folder.

Switch MPM and modules on AlmaLinux and Rocky Linux

On these systems the php package already installs php-fpm and a handler file, /etc/httpd/conf.d/php.conf, that sends .php to the FPM socket /run/php-fpm/www.sock. The event MPM is the default. Check:

Bash
httpd -M | grep -E "mpm|proxy_fcgi"

You want mpm_event_module and proxy_fcgi_module. If prefork is active, edit /etc/httpd/conf.modules.d/00-mpm.conf so only the mod_mpm_event.so line is uncommented. Then start FPM and restart Apache:

Bash
sudo systemctl enable --now php-fpm
sudo apachectl configtest
sudo systemctl restart httpd

Set the PHP handler per site

A global handler is fine for one PHP version. To control PHP per virtual host, put a SetHandler in the vhost instead. Add this inside <VirtualHost>:

Apache
<FilesMatch \.php$>
    SetHandler "proxy:unix:/run/php/php8.3-fpm.sock|fcgi://localhost"
</FilesMatch>

The part before the | is the socket of the FPM pool. The fcgi://localhost part is a label Apache needs; it does not open a network connection.

Find the right socket path for each version:

Bash
ls /run/php/          # Ubuntu and Debian, e.g. php8.2-fpm.sock, php8.3-fpm.sock
ls /run/php-fpm/      # AlmaLinux and Rocky, e.g. www.sock

Using <FilesMatch> rather than ProxyPassMatch means Apache only passes files that really exist, so a request for /missing.php gets a normal 404 from Apache.

Choose a PHP version per virtual host

With two FPM versions installed, point each site at its own socket. One vhost:

Apache
<VirtualHost *:80>
    ServerName legacy.example.com
    DocumentRoot /var/www/legacy/public
    <FilesMatch \.php$>
        SetHandler "proxy:unix:/run/php/php8.2-fpm.sock|fcgi://localhost"
    </FilesMatch>
</VirtualHost>

And another vhost uses /run/php/php8.3-fpm.sock. If you enabled a global phpX.Y-fpm conf earlier, the vhost setting overrides it for that site. To keep things clear, you can disable the global one with sudo a2disconf php8.3-fpm once every vhost has its own handler.

Tip: For full isolation between sites, give each one its own FPM pool and Linux user. See Run each site in its own PHP-FPM pool.

Check it works

Confirm the MPM:

Bash
apachectl -V | grep -i mpm

It should say Server MPM: event. Then create a temporary test file in a site's document root:

Bash
echo '<?php echo php_sapi_name(), " ", PHP_VERSION;' | sudo tee /var/www/example.com/public/sapi.php
curl http://example.com/sapi.php

The output should start with fpm-fcgi followed by the version you expect. Delete the file afterwards:

Bash
sudo rm /var/www/example.com/public/sapi.php

Troubleshooting

  • AH00534: Apache is running a threaded MPM, but your PHP Module is not compiled to be threadsafe: mod_php is still enabled. Disable it with a2dismod phpX.Y and restart.
  • PHP files download instead of running: no handler matches. Check the SetHandler block, or that the global FPM conf is enabled. See PHP files downloading instead of executing.
  • 503 Service Unavailable: Apache cannot reach the socket. Make sure phpX.Y-fpm is running and the socket path in the vhost exists.
  • Settings in .htaccess such as php_value cause a 500 error: those lines only work with mod_php. Move them to the pool's php_admin_value settings or a .user.ini file.
  • Permission denied on the socket (AlmaLinux and Rocky): the socket's listen.owner and listen.group must allow the apache user.

Next steps

Was this guide helpful?

Still stuck?

Ask MdmHost AI for a quick answer, or open a ticket and our team will take a look. We are here 24/7.