Debian

How To Install PHP with Apache on Ubuntu and Debian

On Ubuntu and Debian, one apt install php gets you PHP, Apache and the mod_php module that runs PHP inside Apache, all from the distribution’s own archive. That is the shortest path to a working PHP site, and because nothing comes from a third-party repository, security fixes arrive with the normal apt updates. That matters this month, since the October 2026 PHP2Shell fix for PHP is one of those updates and mod_php is the setup PHP confirmed full code execution against.

Original content from computingforgeeks.com - post 67174

Below we install PHP and the common extensions, wire PHP into Apache on Ubuntu or Debian, prove it serves pages, check that the installed build carries the PHP2Shell patch and tune php.ini. Every command was run again in October 2026 on fresh Ubuntu 24.04 and Debian 13 test systems as a non-root sudo user, and on Ubuntu 26.04 up to the PHP2Shell check, so the outputs below come from those systems.

Installation Pre-requisites

For this setup you will need:

  1. A running Ubuntu / Debian system
  2. Root or sudo access on the server for running privileged commands
  3. Internet connectivity or local APT mirrors to download packages from

Step 1: Package repository update

Ensure your package repository and system is updated to the latest versions.

sudo apt update && sudo apt upgrade -y

If the upgrade installed a new kernel, reboot before you continue so you install PHP on the system you will actually be running. Ubuntu flags a pending reboot by creating /var/run/reboot-required; on Debian, watch the upgrade output for a new linux-image package.

Step 2: Install PHP and extensions

A default version of PHP available in your OS repositories is installed using apt package manager. On a fresh server the php metapackage also pulls in Apache and its PHP module (libapache2-mod-php8.3 on Ubuntu 24.04), and Apache is started and enabled as soon as the install finishes, so don’t be surprised to find port 80 answering after this one command.

sudo apt install -y php

Installed version can be confirmed using php --version command. Ubuntu 24.04 reports PHP 8.3:

php --version

The build date is the October 2026 security update, while the version stays 8.3.6 because Ubuntu backports fixes instead of moving to new upstream releases:

PHP 8.3.6 (cli) (built: Oct 19 2026 15:16:58) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.3.6, Copyright (c) Zend Technologies
    with Zend OPcache v8.3.6, Copyright (c), by Zend Technologies

Debian 13 prints PHP 8.4.27 at the same point and Ubuntu 26.04 prints PHP 8.5.4. Most PHP applications need a handful of extensions on top of the core, and this set covers the usual ones.

sudo apt install -y php-{cli,mbstring,mysql,curl,xml,zip,gd}

The unversioned names resolve to your release’s default PHP branch, so the same line works on Ubuntu and Debian. If an application needs a different branch than the one your release ships, use the Ondrej PPA as shown in the PHP 8.2 on Ubuntu guide instead of mixing branches by hand. The PPA has no Ubuntu 26.04 (resolute) builds yet, so on 26.04 stay with the archive’s PHP. On Debian the equivalent is the Sury repository at packages.sury.org, set up in the PHP on Debian guide.

Step 3: Install Apache and PHP module

When you install Apache on its own, you don’t get the PHP module since it’s not bundled with it. If Step 2 already pulled both in, this command only adds the small libapache2-mod-php metapackage, which keeps the module tracking your release’s default PHP branch, so it is safe to run either way.

sudo apt install -y apache2 libapache2-mod-php

The libapache2-mod-php is the module that enables Apache to support PHP.

After the installation apache2 service is started automatically and enabled to start on system boot, and the package enables the versioned PHP module for you. The module is named after the PHP branch (php8.3, php8.4), so sudo a2enmod php fails with “Module php does not exist”. Enable the rewrite module, which most PHP applications need for clean URLs, and restart Apache to load it:

sudo a2enmod rewrite
sudo systemctl restart apache2

Confirm both modules are loaded. a2query lives in /usr/sbin, which isn’t in a normal user’s path on Debian, hence the sudo.

sudo a2query -m | grep -E 'php|rewrite'

Ubuntu 24.04 lists the PHP 8.3 module next to rewrite (Debian 13 shows php8.4):

php8.3 (enabled by maintainer script)
rewrite (enabled by site administrator)

To enable or disable the PHP module later, use a2enmod and a2dismod with that versioned name, then restart Apache.

Step 4: Create a PHP Info Page

To demonstrate our installation is functioning, create a test PHP file in the default Apache web document root directory.

echo "<?php phpinfo(); ?>" | sudo tee /var/www/html/info.php

Set permissions to allow the www-data user read access to the file. This is the user the Apache process runs as.

sudo chmod 644 /var/www/html/info.php

Open your browser and navigate to http://10.0.1.50/info.php, replacing the address with your server’s IP, to see the full PHP information page. From the server itself, a quick curl pulls out the two lines that matter:

curl -s http://localhost/info.php | grep -oE 'PHP Version [0-9.]+|Apache 2(\.0)? Handler' | head -2

The Server API reads “Apache 2.0 Handler” on Ubuntu 24.04 (“Apache 2 Handler” on Debian 13), which confirms Apache runs PHP through mod_php rather than handing the file out as text:

PHP Version 8.3.6
Apache 2.0 Handler

If you get the raw <?php phpinfo(); ?> line back instead, the PHP module isn’t loaded, so recheck the a2query output from Step 3. Leave the page in place until you finish Step 6, then delete it.

Step 5: Confirm the PHP2Shell patch (CVE-2026-109618, CVE-2026-109619)

PHP2Shell is the name for two critical remote code execution bugs in PHP itself, CVE-2026-109618 and CVE-2026-109619, rated CVSS v4.0 9.2 in php.net’s 20 October pre-announcement and fixed upstream on 21 October 2026 in PHP 8.5.12, 8.4.27, 8.3.36 and 8.2.35, according to the PHP security advisory. The Ubuntu and Debian archives already ship the fix for their default PHP, so a server installed with Step 2 today gets it. Ubuntu backports the patch without changing the version number, which means php --version can’t tell you whether you’re safe, but the package changelog can.

Store your PHP branch in a variable first, so the same commands work on every release. Run the rest of this step and Step 6 in the same shell, or set PHP_VER again if you open a new one.

PHP_VER=$(php -r 'echo PHP_MAJOR_VERSION.".".PHP_MINOR_VERSION;')
zgrep -E 'CVE-2026-10961[89]' /usr/share/doc/php${PHP_VER}-common/changelog.Debian.gz

A patched Ubuntu 24.04 server prints the patch line and both CVE IDs. Debian 13 prints a single line naming both CVEs. Either way, a fresh install can stop here and move on to Step 6:

    - debian/patches/CVE-2026-109618_109619.patch: Avoid state corruption via
    - CVE-2026-109618
    - CVE-2026-109619

Minimized cloud images strip /usr/share/doc, so the changelog may not exist there, and then zgrep prints a gzip “No such file or directory” error instead of nothing. Comparing the installed package version with the fixed build works everywhere:

dpkg --compare-versions "$(dpkg-query -W -f='${Version}' php${PHP_VER:?run the PHP_VER line first}-common)" ge 8.3.6-0ubuntu0.24.04.13 && echo patched || echo VULNERABLE

That line uses the Ubuntu 24.04 build. Swap in the fixed version for your release from this list of what each archive served on 22 October 2026:

  • Ubuntu 26.04: php8.5 8.5.4-0ubuntu1.5
  • Ubuntu 24.04: php8.3 8.3.6-0ubuntu0.24.04.13
  • Ubuntu 22.04: php8.1 8.1.2-1ubuntu2.28
  • Debian 13: php8.4 8.4.27-1~deb13u1
  • Debian 12: php8.2 8.2.35-1~deb12u1

On an existing server that hasn’t been updated since 21 October, the zgrep prints nothing and the comparison prints VULNERABLE, which means the installed build is older than the fixed one. We reproduced that by rolling our servers back to the release builds (8.3.6-0maysync1 on Ubuntu 24.04, 8.4.24-1~deb13u1 on Debian 13). The upgrade glob covers the PHP metapackage and every installed extension of your branch, libapache2-mod-php${PHP_VER} is named on its own because it is the copy of PHP that Apache loads, and libphp${PHP_VER}-embed is listed because the glob doesn’t match it. apt skips anything that isn’t installed:

sudo apt update
sudo apt install --only-upgrade -y "php${PHP_VER}*" "libapache2-mod-php${PHP_VER}" "libphp${PHP_VER}-embed"

apt prints a “Skipping” line for each matching package you don’t have, then upgrades the rest. Our Ubuntu 24.04 server moved 12 packages:

The following packages will be upgraded:
  libapache2-mod-php8.3 php8.3 php8.3-cli php8.3-common php8.3-curl php8.3-gd
  php8.3-mbstring php8.3-mysql php8.3-opcache php8.3-readline php8.3-xml
  php8.3-zip
12 upgraded, 0 newly installed, 0 to remove and 2 not upgraded.

Note when the packages changed, because the restart check compares against that time. dpkg rotates its log, so search the old copies too and sort the result:

zgrep -hE " (install|upgrade) (php${PHP_VER}-common|libapache2-mod-php${PHP_VER}):" /var/log/dpkg.log* | sort | tail -2

Both packages moved from the old build to the fixed one.

2026-10-22 19:55:33 upgrade libapache2-mod-php8.3:amd64 8.3.6-0maysync1 8.3.6-0ubuntu0.24.04.13
2026-10-22 19:55:33 upgrade php8.3-common:amd64 8.3.6-0maysync1 8.3.6-0ubuntu0.24.04.13

With mod_php, PHP lives inside the Apache processes, so Apache is the service to restart. Don’t count on the package doing it: in our test the Ubuntu 24.04 upgrade restarted Apache by itself, but on Debian 13 the Apache main process kept the same PID through the upgrade, which means it was still running the vulnerable code. Restart it and confirm:

sudo systemctl restart apache2
systemctl show -p MainPID -p ActiveEnterTimestamp apache2

The timestamp must be later than the upgrade time from dpkg.log. If it is earlier, Apache never restarted, so run the restart again and check systemctl status apache2. Ours came back after the 19:55:33 upgrade, and the MainPID changed from 32912 to 32952 on the explicit restart, so the new process is the one serving pages:

MainPID=32952
ActiveEnterTimestamp=Thu 2026-10-22 19:55:49 UTC

Finally, ask Apache itself which PHP it is running. A two-line probe page answers through the web server, the path attackers use, rather than through the CLI:

echo '<?php echo PHP_VERSION, " ", PHP_SAPI, PHP_EOL;' | sudo tee /var/www/html/probe.php >/dev/null
curl -s http://localhost/probe.php
sudo rm /var/www/html/probe.php

Ubuntu 24.04 still answers 8.3.6 after the fix because of the backport, so the changelog and the restart time are your proof there. Debian 13 moves to the fixed upstream release, and its probe answers 8.4.27:

8.3.6 apache2handler

Run the zgrep again and it lists both CVE IDs. Restart any long-running PHP CLI workers too (queue workers under supervisor or systemd), because they keep the old code in memory until they restart.

Older releases are a different story. Ubuntu 20.04’s free archive serves php7.4 7.4.3-4ubuntu2.29, which is not patched (the fix is in Ubuntu Pro only), and the Ondrej PPA no longer publishes PHP for 20.04. Debian 11’s php7.4 7.4.33-1+deb11u12 has no fix outside paid ELTS, although the Sury repository still carries fixed builds for bullseye. Rather than patch around an end-of-life base, rebuild on a current release with the PHP 8.5 on Debian guide or the Ubuntu 26.04 PHP 8.5 guide.

Step 6: PHP Configuration optimizations

Locate the default PHP configuration file.

php --ini | grep "Loaded Configuration File"

The command line PHP reports its own file under cli:

Loaded Configuration File:         /etc/php/8.3/cli/php.ini

Apache doesn’t read that one. mod_php loads /etc/php/${PHP_VER}/apache2/php.ini, which is the file to edit for anything your website should see, such as max memory or execution time. If vim isn’t installed (Debian’s default install ships only vim-tiny), add it first with sudo apt install -y vim, then open the file:

sudo vim /etc/php/${PHP_VER}/apache2/php.ini

Find these directives and change their values. The defaults are 128M, 30, 2M and 8M:

memory_limit = 256M
max_execution_time = 120
upload_max_filesize = 10M
post_max_size = 10M

Whenever changes are made in the PHP configuration file, a restart of apache2 service must be done.

sudo systemctl restart apache2

Reload the info page to confirm Apache picked up the change: memory_limit read 256M through the web server on both our Ubuntu and Debian servers after the restart. Logs relating to Apache web server errors are found at /var/log/apache2/error.log, which is the first place to look if a page returns a 500 error:

sudo tail -f /var/log/apache2/error.log

Once the info page shows your settings, delete it, because it hands anyone who finds it your PHP version, paths and loaded modules.

sudo rm /var/www/html/info.php

Apache is now serving PHP through mod_php with a patched PHP build. Keep the PHP packages and libapache2-mod-php on your regular apt upgrade cycle and restart Apache after every PHP update, because that restart is what puts a fix like PHP2Shell into the running server. If you plan to host a CMS next, the WordPress on Debian with Apache guide builds on this exact stack.

Keep reading

Upgrade Ubuntu 24.04 to Ubuntu 26.04 LTS (Step by Step) Ubuntu Upgrade Ubuntu 24.04 to Ubuntu 26.04 LTS (Step by Step) UFW Firewall Commands with Examples on Ubuntu 26.04 / 24.04 Security UFW Firewall Commands with Examples on Ubuntu 26.04 / 24.04 PHP2Shell on End-of-Life PHP 8.1, 8.0 and 7.4 (WHMCS, ionCube) Php PHP2Shell on End-of-Life PHP 8.1, 8.0 and 7.4 (WHMCS, ionCube) Install Zen Browser on Ubuntu 26.04 / 24.04 Ubuntu Install Zen Browser on Ubuntu 26.04 / 24.04 Linux Terminal Shortcuts Cheat Sheet (Bash and zsh) Cheat Sheets Linux Terminal Shortcuts Cheat Sheet (Bash and zsh) How To Install XFCE Desktop on Debian Linux Debian How To Install XFCE Desktop on Debian Linux

Leave a Comment

Press ESC to close