Running PHP through PHP-FPM keeps PHP execution outside the Apache worker process, which makes busy sites easier to tune and avoids tying dynamic requests to the older prefork worker model used by mod_php.
On current Debian and Ubuntu packages, Apache passes PHP requests through mod_proxy_fcgi to a versioned PHP-FPM socket under /run/php. The supported package workflow is to install the distro PHP-FPM package, enable the proxy modules, and enable the matching phpX.Y-fpm.conf snippet that the package places in /etc/apache2/conf-available.
These steps assume the standard /etc/apache2/ layout, helper commands such as a2enmod and a2enconf, and systemd unit names such as apache2 and php8.3-fpm. If mod_php is still loaded, disable it before switching to PHP-FPM because it usually pulls Apache back to mpm_prefork and defeats the cleaner event-based setup that PHP-FPM is meant to support.
Related: How to improve Apache performance
Related: How to enable or disable Apache modules
Related: How to test Apache configuration
$ sudo apt update Hit:1 http://ports.ubuntu.com/ubuntu-ports noble InRelease Hit:2 http://ports.ubuntu.com/ubuntu-ports noble-updates InRelease Hit:3 http://ports.ubuntu.com/ubuntu-ports noble-backports InRelease Hit:4 http://ports.ubuntu.com/ubuntu-ports noble-security InRelease Reading package lists... $ sudo apt install --assume-yes php-fpm Reading package lists... Building dependency tree... Reading state information... The following NEW packages will be installed: php-fpm php8.3-fpm ##### snipped ##### Setting up php8.3-fpm (8.3.6-0ubuntu0.24.04.8) ... NOTICE: Not enabling PHP 8.3 FPM by default. NOTICE: To enable PHP 8.3 FPM in Apache2 do: NOTICE: a2enmod proxy_fcgi setenvif NOTICE: a2enconf php8.3-fpm
php-fpm tracks the distro default PHP version, while the installed service and Apache snippet stay versioned, such as php8.3-fpm.
$ systemctl list-unit-files 'php*-fpm.service' --no-legend php8.3-fpm.service enabled enabled
Use the versioned unit name from this output in later status checks if more than one PHP branch is installed.
$ sudo systemctl is-active php8.3-fpm active $ ls -l /run/php/ total 4 -rw-r--r-- 1 root root 4 Apr 8 04:23 php8.3-fpm.pid srw-rw---- 1 www-data www-data 0 Apr 8 04:23 php8.3-fpm.sock
If the service is not active, start or troubleshoot that versioned unit before wiring Apache to it.
$ ls -1 /etc/apache2/conf-available | grep 'php.*fpm' php8.3-fpm.conf
Enable only the snippet that matches the active socket version. Leaving multiple phpX.Y-fpm.conf snippets enabled can route requests to the wrong PHP branch.
$ sudo a2enmod proxy_fcgi setenvif Considering dependency proxy for proxy_fcgi: Enabling module proxy. Enabling module proxy_fcgi. Module setenvif already enabled To activate the new configuration, you need to run: service apache2 restart
$ apache2ctl -M 2>/dev/null | grep -E 'php|mpm_|proxy_fcgi|setenvif' mpm_event_module (shared) proxy_fcgi_module (shared) setenvif_module (shared)
If the output still shows php_module, disable that mod_php module first. If it shows mpm_prefork_module, switch back to mpm_event after removing mod_php so Apache does not stay on the prefork worker model unnecessarily.
$ sudo a2dismod php8.3 Module php8.3 disabled. To activate the new configuration, you need to run: service apache2 restart
Replace php8.3 with the module name reported by apache2ctl -M. Skip this step when no PHP module is loaded.
$ sudo a2dismod mpm_prefork Module mpm_prefork disabled. To activate the new configuration, you need to run: service apache2 restart $ sudo a2enmod mpm_event Considering conflict mpm_worker for mpm_event: Considering conflict mpm_prefork for mpm_event: Enabling module mpm_event. To activate the new configuration, you need to run: service apache2 restart
On a clean Apache package install, mpm_event is usually already enabled and this step can be skipped.
$ sudo a2enconf php8.3-fpm Enabling conf php8.3-fpm. To activate the new configuration, you need to run: service apache2 reload
The packaged snippet uses mod_proxy_fcgi to forward .php, .phtml, and .phar requests to the matching PHP-FPM socket.
If each virtual host should use a different pool or socket, place the SetHandler block inside that site's <VirtualHost> instead of enabling one global snippet for every site.
$ sudo apache2ctl configtest AH00558: apache2: Could not reliably determine the server's fully qualified domain name, using 127.0.1.1. Set the 'ServerName' directive globally to suppress this message Syntax OK
Related: How to test Apache configuration
$ sudo systemctl restart apache2
If only the phpX.Y-fpm.conf snippet changed and no modules changed, a reload is sufficient, but a restart is the safer apply step after handler or MPM changes.
$ sudo systemctl is-active apache2 php8.3-fpm active active
If either unit is not active, inspect sudo journalctl --unit=apache2 --unit=php8.3-fpm --no-pager --lines=20 before testing requests.
$ cat > /home/user/fpm-test.php <<'EOF'
<?php
header("Content-Type: text/plain");
echo "sapi=" . PHP_SAPI . PHP_EOL;
EOF
$ sudo mv /home/user/fpm-test.php /var/www/html/fpm-test.php
Use the DocumentRoot served by the virtual host being tested when the site is not using the default /var/www/html path.
Do not leave ad-hoc PHP test files in a public document root after verification.
$ curl --silent --show-error --fail http://127.0.0.1/fpm-test.php sapi=fpm-fcgi
sapi=fpm-fcgi confirms the request ran through PHP-FPM, while sapi=apache2handler means mod_php is still handling the request.
$ sudo rm --force /var/www/html/fpm-test.php