PHP with Apache: PHP-FPM, mod_proxy_fcgi and supported versions

Updated November 2026

The modern way to run PHP on Apache httpd 2.4 is PHP-FPM, a separate pool of PHP processes that Apache talks to through mod_proxy_fcgi. It works with the event MPM and HTTP/2, lets each site run as its own user, and lets you restart PHP without touching Apache. The PHP manual now says plainly: do not use mod_php for new installations.

Supported PHP versions

Each PHP branch gets two years of active support (bug and security fixes) and then two years of security fixes only. As of November 2026, php.net lists these branches as supported:

PHP branches supported as of November 2026 (php.net)
BranchFirst releaseActive support untilSecurity fixes untilLatest release
8.5Nov 20, 2025Dec 31, 2027Dec 31, 20298.5.12
8.4Nov 21, 2024Dec 31, 2026Dec 31, 20288.4.27
8.3Nov 23, 2023Dec 31, 2025 (ended)Dec 31, 20278.3.36
8.2Dec 8, 2022Dec 31, 2024 (ended)Dec 31, 20268.2.35

PHP 8.1 reached end of life on December 31, 2025, and 8.0 on November 26, 2023. PHP 8.6.0 is at the release-candidate stage (RC4 came out on October 21, 2026, with RC5 planned for November 5). If you build PHP yourself or use packages straight from php.net, upgrade now if you run anything older than the releases in the table: the October 2026 releases fixed CVE-2026-109618 and CVE-2026-109619, which php.net rates as critical.

Distribution packages follow their own calendar. Debian 13 ships PHP 8.4 and Debian 12 ships 8.2; Ubuntu 24.04 ships 8.3 and Ubuntu 26.04 ships 8.5 (the version behind each release’s php-fpm package). Distributions backport security fixes, so a Debian 12 server with PHP 8.2 can still get fixes after php.net stops, for as long as Debian supports that release. Check your version with php -v.

Why PHP-FPM and not mod_php

mod_php loads the PHP interpreter into every Apache process. Unless PHP is compiled with thread safety (ZTS), which distribution packages normally are not, mod_php only works with the prefork MPM: one process per connection, no threads. That costs a lot of memory, and HTTP/2 under prefork handles only one request per connection at a time. On Debian and Ubuntu, installing libapache2-mod-php even switches Apache to prefork automatically.

With PHP-FPM, Apache can use the event MPM. Static files are served by Apache’s threads without loading PHP, and PHP runs in a fixed pool that you size yourself. On Fedora the packaged configuration already sends PHP to FPM by default and calls mod_php deprecated.

Browser Apache httpd event MPM static files served here PHP-FPM pool runs .php files FastCGI Unix socket, e.g. /run/php/php8.4-fpm.sock
Apache hands only PHP requests to PHP-FPM over FastCGI.

Set up PHP-FPM on Debian and Ubuntu

Install the FPM package for your PHP version. php-fpm picks the distribution’s default version; php8.4-fpm names it exactly. The package does not change Apache by itself; it tells you which two commands to run:

sudo apt install php-fpm
sudo a2enmod proxy_fcgi setenvif
sudo a2enconf php8.4-fpm
sudo apache2ctl configtest
sudo systemctl reload apache2

The enabled file /etc/apache2/conf-enabled/php8.4-fpm.conf sends .php, .phar and .phtml files to the socket /run/php/php8.4-fpm.sock, passes the Authorization header on to PHP, and blocks .phps source files. The FPM service is called php8.4-fpm.

If mod_php was installed before, turn it off and go back to the event MPM. This needs a full restart, not a reload:

sudo a2dismod php8.4 mpm_prefork
sudo a2enmod mpm_event
sudo apache2ctl configtest
sudo systemctl restart apache2

Set up PHP-FPM on RHEL, AlmaLinux, Rocky Linux and Fedora

sudo dnf install php php-fpm
sudo systemctl enable --now php-fpm
sudo apachectl configtest
sudo systemctl reload httpd

The packaged /etc/httpd/conf.d/php.conf sends .php and .phar files to /run/php-fpm/www.sock unless mod_php is loaded, and adds index.php to DirectoryIndex. The default pool in /etc/php-fpm.d/www.conf runs as user apache. The mod_proxy_fcgi module is already loaded by 00-proxy.conf.

Write the handler yourself

For a custom setup, or on a system without packaged glue, put this in the virtual host. It needs mod_proxy, mod_proxy_fcgi and (for the first line) mod_setenvif:

<VirtualHost *:443>
    ServerName example.com
    DocumentRoot /var/www/example.com/public
    DirectoryIndex index.php index.html

    SetEnvIfNoCase ^Authorization$ "(.+)" HTTP_AUTHORIZATION=$1

    <FilesMatch "\.php$">
        <If "-f %{REQUEST_FILENAME}">
            SetHandler "proxy:unix:/run/php/php8.4-fpm.sock|fcgi://localhost"
        </If>
    </FilesMatch>

    # for front-controller frameworks (Laravel, Symfony, WordPress):
    FallbackResource /index.php
    # ... SSLEngine and certificate lines ...
</VirtualHost>

Settings in a virtual host override the global ones, because httpd applies sections inside <VirtualHost> after those outside it. That is how one site can use a different PHP version than the global default.

One pool per site, several PHP versions

Give each site its own FPM pool so that a bug in one site cannot read another site’s files. On Debian, create /etc/php/8.4/fpm/pool.d/example.conf (RHEL: /etc/php-fpm.d/example.conf):

[example]
user = example
group = example
listen = /run/php/php8.4-fpm-example.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660
pm = ondemand
pm.max_children = 10
pm.process_idle_timeout = 10s

On RHEL use apache instead of www-data. Restart FPM (sudo systemctl restart php8.4-fpm), then point that site’s SetHandler at /run/php/php8.4-fpm-example.sock.

Several PHP versions work the same way: install, for example, php8.3-fpm and php8.4-fpm side by side, and each virtual host’s SetHandler names the socket of the version it needs. Your distribution may ship only one version; others then have to come from a separate repository, which also becomes responsible for their security updates.

How many PHP processes?

pm.max_children caps the number of PHP requests running at the same time. Size it from memory, not from guesswork. Measure the average memory of a busy FPM process:

ps -o rss= -C php-fpm8.4 | awk '{s+=$1; n++} END {print s/n/1024 " MB"}'

(On RHEL the process name is php-fpm.) Example: you can give PHP 2 GB and each process uses about 60 MB. 2,048 ÷ 60 ≈ 34, so set pm.max_children = 30 to keep some headroom. Requests above that limit wait in the queue rather than pushing the server into swap.

Check that it works

Create a test file, look at it, and delete it straight away, because it reveals a lot about your server:

echo '<?php phpinfo();' | sudo tee /var/www/example.com/public/info.php
curl -s https://example.com/info.php | grep -o 'FPM/FastCGI' | head -1
sudo rm /var/www/example.com/public/info.php

The Server API line on the page should read FPM/FastCGI. If it reads Apache 2.0 Handler, mod_php is still active.

Common problems

The browser downloads the PHP file or shows its source
No handler is set for .php. On Debian, check that a2enconf php8.4-fpm was run and that the version in the name matches the installed FPM.
503 Service Unavailable
Apache cannot reach the socket. Check that FPM is running (systemctl status php8.4-fpm), that the socket path in SetHandler matches listen in the pool, and that Apache’s user may open it (listen.owner, listen.group). The Apache error log names the socket it tried.
“File not found.” or “Primary script unknown”
FPM got a file name it cannot see: the wrong DocumentRoot, a pool running in a chroot, or a pool user without read permission.
Logins fail behind HTTP authentication or API tokens are ignored
The Authorization header did not reach PHP. Add the SetEnvIfNoCase line shown above.

More error cases are in fixing common errors. With PHP on FPM you can move to the event MPM and turn on HTTP/2.

Sources