Running PHP through PHP-FPM keeps PHP execution outside the Apache worker process, improving isolation and making concurrency easier to tune for busy sites. Separate worker pools also avoid the tight coupling of mod_php to a single Apache MPM, which can reduce memory usage on dynamic sites.
In a PHP-FPM setup, Apache forwards requests for .php scripts to a FastCGI backend using mod_proxy_fcgi, usually via a Unix domain socket under /run/php created by the PHP-FPM service. The PHP-FPM master process manages worker pools, executes scripts, and returns the generated response to Apache.
Handler misconfigurations typically show up as 503 Service Unavailable errors, raw PHP source downloads, or blank pages when the socket path, enabled modules, or permissions do not line up. Commands below assume Debian or Ubuntu with configuration under /etc/apache2, helper commands like a2enmod and a2enconf, and a systemd service named apache2.
Steps to configure PHP-FPM with Apache:
- Install the PHP-FPM package for the PHP version in use.
$ 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 php8.3-fpm libapache2-mod-php8.3 Reading package lists... Building dependency tree... Reading state information... The following additional packages will be installed: php-common php8.3-cli php8.3-common php8.3-opcache php8.3-readline ##### snipped ##### Setting up libapache2-mod-php8.3 (8.3.6-0ubuntu0.24.04.5) ... Module mpm_event disabled. Enabling module mpm_prefork. apache2_switch_mpm Switch to prefork apache2_invoke: Enable module php8.3 Setting up php8.3-fpm (8.3.6-0ubuntu0.24.04.5) ... ##### snipped #####
Package names vary by PHP version, such as php8.2-fpm or php8.3-fpm.
- List installed PHP-FPM units to capture the active service name.
$ systemctl list-units --type=service --all 'php*-fpm.service' --no-pager UNIT LOAD ACTIVE SUB DESCRIPTION php8.3-fpm.service loaded active running The PHP 8.3 FastCGI Process Manager Legend: LOAD → Reflects whether the unit definition was properly loaded. ACTIVE → The high-level unit activation state, i.e. generalization of SUB. SUB → The low-level unit activation state, values depend on unit type. 1 loaded units listed. To show all installed unit files use 'systemctl list-unit-files'.The unit name usually matches the socket name under /run/php.
- Confirm the Unix socket path created by PHP-FPM under /run/php.
$ ls -l /run/php/ total 4 -rw-r--r-- 1 root root 5 Jan 11 05:25 php8.3-fpm.pid srw-rw---- 1 www-data www-data 0 Jan 11 05:25 php8.3-fpm.sock lrwxrwxrwx 1 root root 30 Jan 11 05:25 php-fpm.sock -> /etc/alternatives/php-fpm.sock
The socket is commonly owned by www-data and must be accessible by the Apache runtime user.
- Enable the Apache modules required for FastCGI proxying.
$ sudo a2enmod proxy proxy_fcgi setenvif Enabling module proxy. Considering dependency proxy for proxy_fcgi: Module proxy already enabled Enabling module proxy_fcgi. Module setenvif already enabled To activate the new configuration, you need to run: systemctl restart apache2
- Check the loaded Apache modules for conflicting mod_php or an unexpected MPM.
$ apachectl -M 2>/dev/null | grep -E 'php|mpm_|proxy_fcgi' mpm_prefork_module (shared) php_module (shared) proxy_fcgi_module (shared)
When php_module is loaded, disabling it prevents double-handling of .php requests and usually allows switching away from mpm_prefork_module.
- Disable the loaded mod_php module by name when a PHP module appears in the module list.
$ sudo a2dismod php8.3 Module php8.3 disabled. To activate the new configuration, you need to run: systemctl restart apache2
Replace php8.3 with the module name shown by apachectl -M.
- Disable mpm_prefork when mpm_prefork_module appears in the module list.
$ sudo a2dismod mpm_prefork Module mpm_prefork disabled. To activate the new configuration, you need to run: systemctl restart apache2
- Enable mpm_event when it is absent from the module list.
$ 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: systemctl restart apache2
- Create an Apache FastCGI handler snippet under /etc/apache2/conf-available that points at the PHP-FPM socket.
$ cat > /home/user/php-fpm.conf <<'EOF' <IfModule proxy_fcgi_module> <IfModule setenvif_module> SetEnvIfNoCase ^Authorization$ "(.+)" HTTP_AUTHORIZATION=$1 </IfModule> <FilesMatch "\.php$"> SetHandler "proxy:unix:/run/php/php8.3-fpm.sock|fcgi://localhost/" </FilesMatch> </IfModule> EOF $ sudo mv /home/user/php-fpm.conf /etc/apache2/conf-available/php-fpm.confReplace /run/php/php8.3-fpm.sock with the socket path observed under /run/php.
Some Debian and Ubuntu PHP packages ship a versioned snippet in /etc/apache2/conf-available (for example php8.3-fpm.conf), and enabling only one FastCGI handler snippet avoids double-handling.
- Enable the configuration snippet.
$ sudo a2enconf php-fpm Enabling conf php-fpm. To activate the new configuration, you need to run: systemctl reload apache2
A per-site handler belongs inside the site’s <VirtualHost> block instead of a global conf snippet.
- Validate Apache configuration syntax before applying changes.
$ sudo apachectl configtest
- Restart Apache to apply the updated configuration.
$ sudo systemctl restart apache2
Review /var/log/apache2/error.log when startup fails with 503 errors or missing module messages.
- Confirm the Apache service is active after the restart.
$ sudo systemctl status apache2 --no-pager -l ● apache2.service - The Apache HTTP Server Loaded: loaded (/usr/lib/systemd/system/apache2.service; enabled; preset: enabled) Active: active (running) since Sun 2026-01-11 05:25:47 +08; 34ms ago Docs: https://httpd.apache.org/docs/2.4/ Process: 19333 ExecStart=/usr/sbin/apachectl start (code=exited, status=0/SUCCESS) Main PID: 19336 (apache2) Tasks: 55 (limit: 4546) Memory: 6.3M (peak: 6.5M) CPU: 16ms CGroup: /system.slice/apache2.service ├─19336 /usr/sbin/apache2 -k start ├─19338 /usr/sbin/apache2 -k start └─19339 /usr/sbin/apache2 -k start Jan 11 05:25:47 host systemd[1]: Starting apache2.service - The Apache HTTP Server... Jan 11 05:25:47 host apachectl[19335]: 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 Jan 11 05:25:47 host systemd[1]: Started apache2.service - The Apache HTTP Server. ##### snipped ##### - Create a temporary test script in the default DocumentRoot to confirm the handler uses PHP-FPM.
$ cat > /home/user/fpm-test.php <<'EOF' <?php header("Content-Type: text/plain"); echo "sapi=" . php_sapi_name() . PHP_EOL; EOF $ sudo mv /home/user/fpm-test.php /var/www/html/fpm-test.phpLeaving a test endpoint accessible can leak application behavior and environment details.
- Request the test script from localhost to confirm the SAPI reports fpm-fcgi.
$ curl --silent --show-error --fail http://127.0.0.1/fpm-test.php sapi=fpm-fcgi
sapi=fpm-fcgi indicates PHP-FPM, and sapi=apache2handler indicates mod_php.
- Remove the temporary test script immediately after verification.
$ sudo rm --force /var/www/html/fpm-test.php
Mohd Shakir Zakaria is a cloud architect with deep roots in software development and open-source advocacy. Certified in AWS, Red Hat, VMware, ITIL, and Linux, he specializes in designing and managing robust cloud and on-premises infrastructures.
