Apache httpd, mod_php and PHP-FPM

Apache and PHP: patching mod_php today, moving to PHP-FPM next

If Apache runs PHP through mod_php, PHP sits inside every Apache child process: the configuration the PHP project validated PHP2Shell against, and the one it rates highest risk. Install the fixed module package and fully restart Apache now, then plan the move to PHP-FPM, which php.net recommends for all modern deployments.

Last reviewed against php.net, the Apache httpd documentation, Red Hat and Debian's Apache and PHP packaging.

Two set-ups

Two ways Apache can run PHP

Apache either carries PHP inside itself or passes PHP requests to a separate service. The choice is tied to Apache's multi-processing module (MPM), and it decides both what you restart after an update and how exposed you were to PHP2Shell.

3D attacker at a laptop with alerts, for PHP running inside Apache

mod_php: PHP inside Apache

PHP compiled as an Apache module and loaded into Apache's own processes. Every Apache child carries a PHP interpreter, and PHP reports its server API as apache2handler.

3D network of laptops and servers, for Apache handing PHP to PHP-FPM

mod_proxy_fcgi: PHP handed to PHP-FPM

Apache keeps serving HTTP and passes each PHP request over FastCGI to a separate PHP-FPM service. PHP reports fpm-fcgi, and it can be restarted without touching Apache.

3D server rack, for the prefork MPM's one process per request

The prefork MPM

Apache's non-threaded, pre-forking model, which keeps each request in a process of its own. mod_php needs it unless PHP was built thread safe, and Debian's mod_php package declares that dependency.

3D loop of cloud, gears and padlock, for the threaded event MPM

The event MPM

Apache's threaded model, and the default it picks on almost every modern system. It suits PHP-FPM; it cannot host a mod_php build that is not thread safe.

mod_php PHP-FPM through mod_proxy_fcgi
Where PHP runs Inside each Apache process In separate PHP-FPM worker processes
Apache MPM prefork, unless PHP is a thread-safe (ZTS) build event, Apache's usual default
PHP_SAPI reports apache2handler fpm-fcgi
Debian 13 package libapache2-mod-php8.4 php8.4-fpm
Restart after a PHP update Apache PHP-FPM; Apache can stay up
php.net's position Do not use for new installations Recommended for modern deployments
PHP2Shell Remote code execution validated; rated highest risk Likely susceptible; one route validated

Highest risk

Why mod_php servers come first

The PHP project's advisories were tested against Apache with mod_php, default limits and the prefork MPM. For CVE-2026-109618 that is the setup where remote code execution was validated. For CVE-2026-109619 both confirmed routes, PHP 8.5's fatal error backtraces and session upload progress, were shown against mod_php.

php.net's advisory names mod_php as the highest-risk setup, and Red Hat's bulletin says environments running mod_php with the default configuration are at higher risk. If your servers mix set-ups, patch the mod_php ones before anything else.

php.net's position on mod_php

The PHP manual's Apache installation page opens with "Do not use mod_php for new installations". It keeps the mod_php steps for reference and for the rare cases that truly need PHP embedded in Apache, and points everyone else to PHP-FPM with mod_proxy_fcgi, citing better resource management, process isolation, restarting PHP without restarting Apache, and compatibility with the event MPM.

Can the site not be patched or moved quickly? Use the risk calculator to decide whether it should stay online meanwhile.

Check first

Find out which one your server uses

Ask Apache which MPM and modules it has loaded. Debian and Ubuntu load mod_php under the name php_module.

# Debian and Ubuntu: the MPM Apache is using
a2query -M

# Loaded modules: php_module means mod_php, proxy_fcgi_module means FastCGI
sudo apache2ctl -M | grep -E 'php|proxy_fcgi'

# RHEL and its rebuilds
sudo httpd -M | grep -E 'php|proxy_fcgi'

A prefork MPM with php_module is mod_php. An event MPM with proxy_fcgi_module is PHP-FPM. On RHEL 8 and later, Apache runs PHP through PHP-FPM by default. A hosting control panel can also tell you, and so can the server API the site reports, explained on how to check your PHP version.

Step by step

Updating PHP while it still runs as mod_php

Do this today, even if a move to PHP-FPM is planned. The commands are for Debian and Ubuntu; panel-managed servers update through cPanel or Plesk instead.

  1. 1. Install the fixed packages

    sudo apt update
    sudo apt upgrade
  2. 2. Check the module package you now have

    # The exact mod_php package version, to compare with the advisory
    dpkg-query -W 'libapache2-mod-php*'

    On Debian 13 the fixed version is 8.4.27-1~deb13u1. Other releases keep older-looking numbers with the fix backported; the Linux guide lists them.

  3. 3. Test the configuration and restart Apache in full

    sudo apache2ctl configtest
    sudo systemctl restart apache2       # RHEL and its rebuilds: httpd

    With mod_php, restarting Apache is restarting PHP. A full service restart replaces the parent process as well as every child; see restarting Apache for why a graceful restart is not the step to rely on.

  4. 4. Confirm

    Use the checks below.

If the fixed package is not there yet

Until the patched package arrives, the advisories offer stopgaps, none of them a full mitigation. Under mod_php they can go in Apache's configuration, where application code cannot undo them, or in a file under /etc/php/8.4/apache2/conf.d/. LimitRequestBody is the request size limit in front of PHP that the advisory suggests; its default is 1 GiB from httpd 2.4.54, and unlimited before that. Restart Apache after any change.

# Server or virtual host configuration, not .htaccess:
# php.net says php_admin_flag cannot be used in .htaccess files
<IfModule php_module>
    php_admin_flag session.upload_progress.enabled off
    # PHP 8.5 only
    php_admin_flag fatal_error_backtraces off
</IfModule>

# A ceiling on request bodies before PHP sees them (in bytes; 10 MiB here)
LimitRequestBody 10485760

The longer-term change

Moving a site from mod_php to PHP-FPM

On Debian and Ubuntu the move takes a handful of commands, because the FPM package already ships the Apache configuration it needs. Stay on the PHP branch you run today so that only one thing changes, and test on a staging copy first if you have one.

Before you start: settings that live in Apache

php.net documents php_value, php_flag, php_admin_value and php_admin_flag as settings for PHP running as an Apache module. Search the Apache configuration and every .htaccess file for them and move each one: per-directory values into .user.ini files, which PHP reads only under CGI and FastCGI, and server-wide values into the FPM pool or php.ini.

# .htaccess under mod_php
php_flag display_errors off
; .user.ini in the same directory, read by PHP under FastCGI
display_errors = Off

PHP re-reads .user.ini files every 300 seconds by default, so allow five minutes before judging a change.

The switch

# 1. Install PHP-FPM for the branch you already run (Debian 13: 8.4)
sudo apt install php8.4-fpm

# 2. Swap mod_php and prefork for FastCGI and the event MPM
sudo a2dismod php8.4 mpm_prefork
sudo a2enmod mpm_event proxy_fcgi setenvif
sudo a2enconf php8.4-fpm

# 3. Check the configuration, then restart Apache
sudo apache2ctl configtest
sudo systemctl restart apache2
  • Order matters. Debian's mod_php module file declares that it depends on prefork, and the event MPM's file declares a conflict with prefork, so mod_php and prefork go first.
  • Debian's a2enmod asks for a restart, not a reload, after module changes, and Apache refuses to start with more than one MPM loaded. The configuration test catches most mistakes first.
  • The enabled php8.4-fpm configuration sends files ending .php, .phar and .phtml to /run/php/php8.4-fpm.sock, and refuses requests for raw .phps source files.
  • mod_php's php.ini changes do not follow you: PHP-FPM reads /etc/php/8.4/fpm/, not /etc/php/8.4/apache2/.

If you need to go back

sudo a2disconf php8.4-fpm
sudo a2dismod mpm_event
sudo a2enmod mpm_prefork php8.4
sudo systemctl restart apache2

Rolling back puts the site on mod_php again, so only do it on a patched build. On RHEL 8 and later there is usually nothing to move: PHP-FPM is already the default. Pools, restarts and running several branches are covered in the PHP-FPM guide.

Restarting Apache

Graceful, restart, or a full service restart?

Apache has three ways to start again, and they replace different amounts of the running server. apachectl's graceful and restart run a syntax check first and refuse to go ahead if it fails. A systemd restart stops Apache before it starts it again, and apachectl's start does no such check, so a broken configuration leaves the site down: run apache2ctl configtest first, every time.

Command What Apache does After a PHP update
sudo apache2ctl graceful Sends SIGUSR1. Each child finishes its current request and is then replaced; the parent process stays up and re-reads its configuration. Not the step to rely on after updating PHP.
sudo apache2ctl restart Sends SIGHUP. Children are stopped at once and replaced; the parent stays up and re-reads its configuration. Every child is new, but the original parent process is still running.
sudo systemctl restart apache2 systemd stops the whole service, parent included, then starts it again. On Debian and Ubuntu the stop is apachectl graceful-stop, so requests already running are allowed to finish first. The one to use after updating mod_php. On RHEL the service is httpd.

Red Hat's bulletin gives systemctl restart httpd for mod_php and rejects a reload, as workers that survive one may still carry affected state. On Debian and Ubuntu, systemctl reload apache2 is apachectl graceful under another name, so it falls short in the same way. Under PHP-FPM the restart that matters is of the FPM service, and Apache can carry on.

Confirm it worked

Check what Apache is serving now

# Which PHP modules Apache has loaded now
sudo apache2ctl -M | grep -E 'php|proxy_fcgi'

# "Active: active (running) since" should be after your restart
systemctl status apache2

Then ask a page on the site. Upload this file, load it once and delete it straight away:

<?php
// sapi-check.php: open it once, then delete it from the server
echo phpversion(), ' via ', php_sapi_name();
  • apache2handler means mod_php is still in use; fpm-fcgi means the move to PHP-FPM worked.
  • Expect your branch's fixed release. On a distribution build, the package version from step 2 is the one to compare with the advisory.
  • Check every virtual host that matters. A site with its own handler configuration can end up on a different PHP from its neighbours.

Since 26 October 2026 both vulnerabilities have been in CISA's Known Exploited Vulnerabilities catalogue. If this server was reachable while it ran a vulnerable mod_php, the update does not remove anything already left behind. Has your PHP site been compromised?

Troubleshooting

When Apache or the site misbehaves

a2enmod says a module "is enabled - cannot proceed due to conflicts"

Debian's a2enmod will not enable an MPM while another is enabled. Disable php8.4 and mpm_prefork first, as in the move above, then enable mpm_event.

Apache will not start: "More than one MPM loaded"

Apache allows exactly one MPM. Something still loads the old one: check mods-enabled for leftover prefork links, or on RHEL the LoadModule lines in the MPM configuration file.

Pages return a 500 error after the move to PHP-FPM

Look for php_value or php_flag lines in .htaccess files. They belong to mod_php, and Apache's own .htaccess guide shows that a directive it cannot use produces a server error. The error log names the file. Move the settings to .user.ini or the FPM pool.

PHP files are sent to the browser instead of running

Apache has no PHP handler for the site. Check that the php8.4-fpm configuration is enabled with a2enconf, and that proxy_fcgi_module appears in apache2ctl -M.

Your php.ini changes disappeared after the move

Each server API has its own configuration tree on Debian and Ubuntu: /etc/php/8.4/apache2/ for mod_php and /etc/php/8.4/fpm/ for PHP-FPM. Copy the changes you made into the FPM tree.

The site still reports apache2handler

mod_php is still enabled, or Apache was not restarted. a2query -m php8.4 tells you whether the module is on; disable it and restart Apache in full.

Quick answers

Apache, mod_php and PHP2Shell: questions, answered

What is mod_php?

mod_php is PHP built as a module for the Apache web server. Apache loads it into its own worker processes, so PHP runs inside Apache rather than as a separate service. On Debian and Ubuntu it is packaged as libapache2-mod-php followed by the branch, and PHP reports its server API as apache2handler.

Is mod_php deprecated?

php.net now says not to use mod_php for new installations, keeps its mod_php instructions for historical reference and for the rare cases that need PHP embedded in Apache, and recommends PHP-FPM with mod_proxy_fcgi for modern deployments. Existing mod_php servers still receive the fixed releases.

Is apachectl graceful enough after updating mod_php?

Use a full restart instead. A graceful restart lets children finish their requests and keeps the parent process running. Red Hat's advice for these vulnerabilities is a full restart, systemctl restart httpd for mod_php, rather than any kind of reload.

Does moving to PHP-FPM fix PHP2Shell on its own?

No. PHP-FPM is affected too: the PHP project treats it as likely susceptible and validated one of the CVE-2026-109619 routes against it. The fix is still the patched PHP release for your branch, or the fixed package from your distribution. Moving to FPM is the longer-term change that takes you off the highest-risk setup.

The PHP2Shell advisory in full

Can I keep mod_php once it is patched?

The patched release closes both vulnerabilities whichever way PHP runs. Keeping mod_php is a decision about the next flaw: php.net advises against it for new installations, it ties you to the prefork MPM, and the PHP project rated it the riskiest setup this time.

What does Apache use on RHEL?

On RHEL 8 and later the default deployment runs PHP through PHP-FPM, not mod_php; Red Hat names mod_php as the default only on RHEL 7. Restart php-fpm after updating PHP there, and httpd only if you changed its configuration.

Updating PHP-FPM

Running the official Docker php image's apache tags? They use mod_php with prefork too: see the Docker guide. Hosting panels, Windows and the other routes are on the update hub.

3D rocket illustration above the call to find every exposed PHP server

Know your exposure

Find every PHP server you run

Patching the server you know about is the easy part. A vulnerability scan finds the forgotten staging site, the old microsite and the appliance with PHP inside, then tells you which ones are still exposed.