Debian ships PHP in its main archive, so you can install PHP on Debian with one apt command and no extra repository. Debian 13 (trixie) installs PHP 8.4 and Debian 12 (bookworm) installs PHP 8.2. The php package also brings in Apache with libapache2-mod-php, which means a fresh server can serve PHP pages as soon as the install finishes, and every extension you need comes from the same archive.
Below you install PHP and the common extensions, check the build against PHP2Shell, and confirm Apache runs it. The install, the PHP2Shell check and the Apache test were run in October 2026 on fresh Debian 13 and Debian 12 systems, plus an upgrade of an unpatched PHP server on each release, and the output blocks come from those runs.
Update system
As always, we recommend updating software repositories and installed packages to sync with latest releases. That run also pulls the PHP security updates Debian publishes through its security suite, so do not skip it on a server that already has PHP.
sudo apt update && sudo apt -y upgrade
If the upgrade installs a new kernel, reboot before you continue so the server runs what is on disk.
Install PHP on Debian
Install the php metapackage, which picks the default PHP branch for your release. It also installs and starts Apache with mod_php, because the PHP package needs a web server module and apt takes the first choice, libapache2-mod-php. On our test systems this one line installed 40 packages on Debian 13 and 41 on Debian 12. These are Debian’s default branches. To run PHP 8.4 on Debian 12 too, from the Sury repository, follow the PHP 8.4 on Debian guide instead.
sudo apt -y install php php-common
Confirm PHP version.
php -v
Debian 13 reports PHP 8.4 with the build date of the security update:
PHP 8.4.27 (cli) (built: Oct 21 2026 07:53:46) (NTS)
Copyright (c) The PHP Group
Built by Debian
Zend Engine v4.4.27, Copyright (c) Zend Technologies
with Zend OPcache v8.4.27, Copyright (c), by Zend Technologies
Debian 12 installs the PHP 8.2 branch:
PHP 8.2.35 (cli) (built: Oct 21 2026 06:29:51) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.2.35, Copyright (c) Zend Technologies
with Zend OPcache v8.2.35, Copyright (c), by Zend Technologies
Treat that version line as a hint only. Debian can patch a package without changing the upstream number, so the check below reads the package itself.
Confirm the PHP2Shell fix (CVE-2026-109618 and CVE-2026-109619)
PHP2Shell is a pair of critical remote code execution bugs in PHP itself, CVE-2026-109618 and CVE-2026-109619, which php.net scored CVSS v4.0 9.2 in its 20 October pre-announcement. Upstream fixed both on 21 October 2026 in PHP 8.5.12, 8.4.27, 8.3.36 and 8.2.35. The Debian packages this guide installs already carry the fix: Debian 13 gets 8.4.27-1~deb13u1 from trixie-security (DSA-6574-1) and Debian 12 gets 8.2.35-1~deb12u1 from bookworm-security (DLA-4848-1). The full write-up is in the PHP security advisory.
Read your package changelog, because Debian names the CVEs in the entry that fixes them. The glob matches whichever PHP 8 branch your release installed:
zgrep -E 'CVE-2026-10961[89]' /usr/share/doc/php8.*-common/changelog.Debian.gz
Debian 13 prints this line:
+ (CVE-2026-109618, CVE-2026-109619): Avoid state corruption via
Debian 12 words its entry differently, and either line means the build is patched. No output at all means the installed package is older than the fixed one. On a server with more than one PHP 8 branch, each line starts with the changelog’s file name, so read the result for every branch:
+ Fix CVE-2026-109618 and CVE-2026-109619: State corruption via bailout.
Minimal images and some cloud templates strip /usr/share/doc, which leaves nothing to grep. The installed package version answers the same question without the docs. Empty output means no PHP 8 package is installed at all:
dpkg -l 'php8.*-common' | grep ^ii
On a patched Debian 13 server you see the security build:
ii php8.4-common 8.4.27-1~deb13u1 amd64 documentation, examples and common module for PHP
Anything at or above 8.4.27-1~deb13u1 on Debian 13 or 8.2.35-1~deb12u1 on Debian 12 is fixed. Our unpatched test systems showed 8.4.24-1~deb13u1 and 8.2.32-1~deb12u1, which are the builds in the release pockets. Seeing one of those means your server has not been upgraded since 21 October, or the security suite is missing from its apt sources, which apt-cache policy php8.4-common (or php8.2-common) shows at once.
A fresh install from this guide can stop here and move on to the extensions. On an existing server that came back vulnerable, upgrade every installed PHP 8 package. The Apache module and the embed library start with lib, so the first glob misses them and they get their own. Apt prints a Skipping line for each matching package that isn’t installed (66 of them on Debian 12), which is harmless:
sudo apt update
sudo apt install -y --only-upgrade 'php8.*' 'libapache2-mod-php8.*' 'libphp8.*'
The upgrade alone does not finish the job. On both releases the package restarted the PHP-FPM service by itself, but Apache kept its PID and kept serving the old mod_php build, so the test page still answered 8.4.24 apache2handler on Debian 13 until Apache restarted. Restart it:
sudo systemctl restart apache2
If PHP-FPM is installed as well, restart it by its versioned name, php8.4-fpm on Debian 13 or php8.2-fpm on Debian 12, even though the package restarted it in our test. A pattern such as 'php8.*-fpm' left the unit running with its old PID on Debian 13, so type the full name. Long-running PHP CLI workers, such as queue consumers under supervisor or their own systemd unit, hold the old code in memory too and need the same restart:
sudo systemctl restart php8.4-fpm
Now prove the running service is newer than the package on disk. This prints the last PHP install or upgrade from dpkg’s logs, rotated ones included:
zgrep -hE ' (install|upgrade) php8\.[0-9]-(common|fpm):' /var/log/dpkg.log* | sort | tail -1
On the Debian 13 test system it showed the move from 8.4.24 to 8.4.27:
2026-10-22 19:53:59 upgrade php8.4-common:amd64 8.4.24-1~deb13u1 8.4.27-1~deb13u1
Compare it with the moment your Apache came back up. Swap apache2 for php8.4-fpm or php8.2-fpm on an FPM site:
systemctl show -p ActiveEnterTimestamp apache2
The timestamp has to be later than the dpkg line, as it is here:
ActiveEnterTimestamp=Thu 2026-10-22 19:54:21 UTC
Finally request a PHP page through the web server, such as the test page from the Apache section below. After the restart, Debian 13 answered 8.4.27 apache2handler and Debian 12 answered 8.2.35 apache2handler. The CLI cannot tell you this, because php -v starts a new process and always reports the package on disk. If the server runs PHP from the Sury repository instead, the PHP2Shell patch guide for Debian covers that source too.
Debian 11 and Debian 10 are outside free Debian support, and that matters here. Their own PHP builds, 7.4 on Debian 11 and 7.3 on Debian 10, get the PHP2Shell fix only through Freexian’s paid ELTS (7.4.33-1+deb11u14 in ELA-1853-1 and 7.3.31-1~deb10u16 in ELA-1854-1, per the ELTS tracker). Debian 11’s free archive still serves the vulnerable 7.4.33-1+deb11u12.
Without ELTS, Debian 11 still has a free way out, because Sury’s repository publishes patched PHP builds for bullseye, and the PHP 8.2 on Debian guide sets that up. Debian 10 has no Sury repository any more, so the free fix there is a release upgrade, one release at a time: Debian 10 to 11 first, then 11 to 12, then 12 to 13.
Until the server runs a patched build, PHP’s advisory lists partial mitigations. With mod_php, put them in Apache’s configuration as php_admin settings, because an application can’t override those with ini_set() or .htaccess. We tested this on a Debian 12 test system with PHP 8.2 and mod_php, which uses the same layout as Debian 10 and 11. The PHP 7.3 and 7.4 servers themselves were not run. Create the file:
sudo vim /etc/apache2/conf-available/php2shell.conf
Add the settings below. Apache names the PHP 8 module php_module and the PHP 7 one php7_module, so the file carries both blocks and works on Debian 10, 11 and 12. If PHP is not loaded into Apache at all (an FPM site), both blocks are skipped and Apache still starts, which we confirmed after switching the test system to FPM:
<IfModule php_module>
php_admin_value memory_limit -1
php_admin_flag session.upload_progress.enabled off
php_admin_value post_max_size 8M
</IfModule>
<IfModule php7_module>
php_admin_value memory_limit -1
php_admin_flag session.upload_progress.enabled off
php_admin_value post_max_size 8M
</IfModule>
If the site accepts uploads, use php_admin_value post_max_size 32M instead and add php_admin_value upload_max_filesize 16M in each block, so a single file always fits inside the request limit.
Enable the snippet, let Apache check the syntax, and restart it so every worker picks it up. A typo stops the chain at configtest instead of taking the site down:
sudo a2enconf php2shell && sudo apache2ctl configtest && sudo systemctl restart apache2
Check the values through Apache, not with php -r, because the CLI reads its own config and still reported session.upload_progress.enabled as 1 on our test system. This probe prints the three settings:
echo '<?php echo ini_get("memory_limit"), " ", ini_get("session.upload_progress.enabled"), " ", ini_get("post_max_size"), "\n";' | sudo tee /var/www/html/ini.php
curl -s http://localhost/ini.php
Before the snippet the page answered 128M 1 8M. After it, the last line reads like this:
-1 0 8M
A script that called ini_set('memory_limit', '64M') still read back -1, which is the point of php_admin_value. The advisory also says to check that no pool, directory or application setting puts a finite memory limit back. On Debian 11 with PHP-FPM instead of mod_php, the same three settings go into /etc/php/7.4/fpm/pool.d/www.conf as php_admin_value[memory_limit] = -1, php_admin_flag[session.upload_progress.enabled] = off and php_admin_value[post_max_size] = 8M, followed by sudo systemctl restart php7.4-fpm; we did not run that variant. Delete the probe with sudo rm /var/www/html/ini.php when you are done. An unlimited memory_limit lets a single request use all the RAM it wants, so watch memory and remove the snippet with sudo a2disconf php2shell and an Apache restart once the server runs a patched build. None of these lines is a fix.
Installing PHP Extensions on Debian
PHP extensions can be installed on demand. Here I’ll show installation of most common PHP extensions required for basic usage.
sudo apt -y install php-cli php-fpm php-json php-pdo php-mysql php-zip php-gd php-mbstring php-curl php-xml php-pear php-bcmath
The line installs cleanly on both releases. JSON and PDO are built into PHP 8, so php-json arrives as a small dependency package and apt satisfies php-pdo with the php8.x-common package already installed. php-fpm installs and starts php8.4-fpm (php8.2-fpm on Debian 12), but Apache keeps running PHP through mod_php. The package prints a NOTICE with the a2enmod proxy_fcgi setenvif and a2enconf steps for switching Apache to FPM, and you can ignore it if mod_php suits you.
Using PHP with Apache Web Server
The php package already installed Apache with mod_php in the install step. For a full LAMP server with a database, see below:
If you installed PHP without the metapackage, install libapache2-mod-php on its own:
sudo apt -y install libapache2-mod-php
Enable Apache module if not already enabled then restart the Web Server. The module is php8.4 on Debian 13, so use php8.2 on Debian 12. The package normally enables it for you, in which case a2enmod answers Module php8.4 already enabled:
sudo a2enmod php8.4
sudo systemctl restart apache2
Check that Apache hands .php files to PHP with a one-line test page that prints the PHP version and the handler running it:
echo '<?php echo PHP_VERSION, " ", PHP_SAPI, "\n";' | sudo tee /var/www/html/info.php
curl -s http://localhost/info.php
The first line is tee echoing the file back, the second is Apache running it. Debian 12 answered with this:
<?php echo PHP_VERSION, " ", PHP_SAPI, "\n";
8.2.35 apache2handler
Debian 13 printed 8.4.27 apache2handler. If you get the PHP source back instead of a version, mod_php is not loaded, so rerun the a2enmod line. Delete /var/www/html/info.php when you are done, because it tells anyone which PHP build the server runs.
For the front end of the sites you build on PHP, this reading list covers web design: