How to configure PHP-FPM with Apache

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.

Steps to configure PHP-FPM with Apache:

  1. Install the distro PHP-FPM package if it is not already present.
    $ 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.

  2. List the installed PHP-FPM unit files to capture the exact service name.
    $ 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.

  3. Confirm the chosen PHP-FPM unit is active and that its socket exists under /run/php.
    $ 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.

  4. Locate the matching Apache configuration snippet shipped by the PHP-FPM package.
    $ 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.

  5. Enable the Apache modules required for FastCGI proxying.
    $ 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
  6. Check the loaded Apache modules before switching the PHP handler.
    $ 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.

  7. Disable the loaded mod_php module when php_module still appears in the module list.
    $ 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.

  8. Disable mpm_prefork and enable mpm_event if the module check still shows mpm_prefork_module.
    $ 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.

  9. Enable the versioned PHP-FPM Apache snippet that matches the active service.
    $ 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.

  10. Validate Apache configuration syntax before applying the new handler.
    $ 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
  11. Restart Apache so any module enablement, module removal, and the new handler configuration all take effect together.
    $ 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.

  12. Confirm both Apache and the selected PHP-FPM service are active after the restart.
    $ 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.

  13. Create a temporary PHP test file under the active site DocumentRoot.
    $ 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.

  14. Request the test file locally to confirm that Apache is handing the request to PHP-FPM instead of mod_php.
    $ 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.

  15. Remove the temporary test file as soon as the handler check succeeds.
    $ sudo rm --force /var/www/html/fpm-test.php