Yesterday I posted how to install Node.js 26 with apt so the operating system keeps it patched after October's security releases. Today it's PHP's turn, for the same reason: security breaches keep coming, and PHP has had its fair share.
On September 24, PHP 8.5.11 fixed more than ten CVEs in OpenSSL, FPM, SOAP, Phar, and the HTTP stream wrapper, including a heap buffer overflow and a TLS hostname verification bypass. On October 21, PHP 8.5.12 went further: an emergency release for two critical vulnerabilities (CVE-2026-109618 and CVE-2026-109619, CVSS 9.2) that allow unauthenticated remote code execution. In php.net's own words, "updating is the only safe choice". The 8.4, 8.3, and 8.2 branches got the same fixes.
Here's the catch: Ubuntu 26.04 LTS (Resolute Raccoon) ships PHP 8.5 in its archive, but it freezes the version at release time and backports the fixes it considers important. At the time of writing, apt gives you PHP 8.5.4 with patches on top, while php.net is already at 8.5.12. To be fair, Ubuntu's security team handles the urgent stuff well: it shipped the October RCE fixes the same day as php.net. But the September batch took 25 days to land, and upstream's regular bug fixes rarely make it at all.
If you want the full upstream release, the easiest way is still to let the operating system handle it. Install PHP with apt from Ondřej Surý's repository, the same packages that have powered PHP on Debian and Ubuntu for more than a decade, and let sudo apt upgrade take care of the rest.
Support dates at a glance
Each PHP branch gets two years of active support followed by two years of security-only fixes. This is where things stand according to the official supported versions page:
| Version | Released | Active support until | Security support until |
|---|---|---|---|
| 8.2 | Dec 8, 2022 | Dec 31, 2024 | Dec 31, 2026 |
| 8.3 | Nov 23, 2023 | Dec 31, 2025 | Dec 31, 2027 |
| 8.4 | Nov 21, 2024 | Dec 31, 2026 | Dec 31, 2028 |
| 8.5 | Nov 20, 2025 | Dec 31, 2027 | Dec 31, 2029 |
If you're still on PHP 8.2, you have less than two months left. PHP 8.6 is scheduled for November 19, 2026, but PHP 8.5 is the safe bet to pair with an LTS distribution today. You can always try 8.6 side by side (see below).
On the OS side, Ubuntu 26.04 LTS was released in April 2026 and gets standard security maintenance until May 2031, extended to 2036 with Ubuntu Pro. So this setup will last for a while.
What's new in PHP 8.5
If you're coming from PHP 8.4 or older, here are a few reasons to upgrade beyond security. The release announcement has the full list, but my favorites are:
- The pipe operator, to chain callables from left to right without temporary variables.
clone()with properties, which turns the "with-er" pattern forreadonlyclasses into one line.- The
#[\NoDiscard]attribute, which warns when someone ignores a return value that matters. - A native URI extension, with URL parsing that follows RFC 3986 and WHATWG, no more
parse_url()surprises. array_first()andarray_last(). Finally.
Here's the pipe operator turning a title into a slug:
$slug = $title
|> trim(...)
|> (fn (string $str): string => str_replace(' ', '-', $str))
|> strtolower(...);
What changed: goodbye PPA, hello packages.sury.org
If you've installed PHP on Ubuntu before, you probably ran add-apt-repository ppa:ondrej/php. That's no longer the way to go: Ondřej Surý is moving his Ubuntu packages from Launchpad to packages.sury.org, the server that already hosts the Debian ones. The resolute packages for Ubuntu 26.04 are only published there.
If you still have the PPA configured, you may have seen apt update complain this summer that the repository changed its 'Label' value, an error that made apt-get update fail in scripts until you accepted the change. For now the PPA still gets updates for Ubuntu 22.04 and 24.04, but it's being merged into packages.sury.org. And if both are configured, apt keeps preferring the PPA builds, so remove it before following the steps below:
sudo add-apt-repository --remove ppa:ondrej/php
The new setup also improves how the signing key is handled. Instead of downloading a raw .gpg file, you install the official debsuryorg-archive-keyring package.
The easy way
As usual, you can install everything with a single command. The script adds the Sury repository, installs PHP 8.5 with the most common extensions, makes it the default php, and optionally installs Composer.
curl -fsSL https://angelblanco.dev/scripts/ubuntu-install-php-85.sh | bash
The script asks for confirmation at each major step. As always, read the script before you pipe it into bash. Trusting random URLs with sudo is a great way to learn about incident response.
If you don't have curl installed:
sudo apt update
sudo apt install curl
The step-by-step guide
Here's what the script does, in case you want to run it step by step.
1. Install the prerequisites
sudo apt update
sudo apt install -y ca-certificates curl unzip
PHP doesn't need unzip, but Composer uses it to extract packages much faster than its PHP fallback.
2. Install the Sury signing key
curl -fsSLo /tmp/debsuryorg-archive-keyring.deb https://packages.sury.org/debsuryorg-archive-keyring.deb \
&& sudo dpkg -i /tmp/debsuryorg-archive-keyring.deb \
&& rm /tmp/debsuryorg-archive-keyring.deb
This drops the key in /usr/share/keyrings/debsuryorg-archive-keyring.gpg (plus, for now, a global copy in /etc/apt/trusted.gpg.d/). On Debian the repository also ships the keyring package, so key rotations arrive with apt upgrade. The Ubuntu suites don't ship it yet, so if the key is ever rotated, download and install the .deb again.
3. Add the Sury repository
Since 24.04, Ubuntu uses the deb822 format (.sources files) for its own repositories, and the classic one-line .list format is now officially deprecated. The codename is read from /etc/os-release, so the same snippet also works on Ubuntu 22.04 and 24.04, and on derivatives like Linux Mint.
sudo tee /etc/apt/sources.list.d/php.sources > /dev/null <<EOF
Types: deb
URIs: https://packages.sury.org/php/
Suites: $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}")
Components: main
Architectures: $(dpkg --print-architecture)
Signed-By: /usr/share/keyrings/debsuryorg-archive-keyring.gpg
EOF
sudo apt update
4. Install PHP 8.5
sudo apt install -y php8.5-cli php8.5-mbstring php8.5-curl php8.5-zip php8.5-xml
Notice that we install php8.5-cli instead of the bare php8.5 metapackage. The metapackage pulls in Apache and its PHP module, which you probably don't want on your development machine. If you need a web server, add php8.5-fpm and pair it with Nginx or Caddy. Need a database? Add php8.5-mysql, php8.5-pgsql, or php8.5-sqlite3.
You don't need an apt pin: Sury's 8.5.12 has a higher version than Ubuntu's 8.5.4, so apt picks it automatically. You can check where each package comes from with:
apt policy php8.5-cli
5. Make PHP 8.5 the default
The php command is managed by update-alternatives, so you can choose which version answers to it:
sudo update-alternatives --set php /usr/bin/php8.5
php --version
6. Install Composer
The official installer publishes a SHA-384 checksum, so verify it before running anything:
EXPECTED_CHECKSUM="$(curl -fsSL https://composer.github.io/installer.sig)"
curl -fsSLo composer-setup.php https://getcomposer.org/installer
ACTUAL_CHECKSUM="$(php -r "echo hash_file('sha384', 'composer-setup.php');")"
if [ "$EXPECTED_CHECKSUM" = "$ACTUAL_CHECKSUM" ]; then
php composer-setup.php --quiet
sudo install -m 0755 composer.phar /usr/local/bin/composer
rm composer.phar
composer --version
else
>&2 echo "ERROR: Invalid Composer installer checksum"
fi
rm composer-setup.php
Using install instead of mv makes root the owner of the binary, which matters because you'll run sudo composer self-update later.
What about other PHP versions?
This is where the Sury repository really shines. It publishes every branch from php5.6 to php8.5, plus the PHP 8.6 release candidates, and they can all live side by side:
sudo apt install -y php8.4-cli php8.4-mbstring php8.4-curl php8.4-zip php8.4-xml
sudo update-alternatives --config php
update-alternatives --config php shows an interactive menu to switch the default php. You can also call a specific version directly with php8.4 or php8.5, which is handy for running a legacy project's tests without touching your default.
If you try 8.6 before its final release on November 19, check apt policy php8.6-cli: builds older than RC4 don't include the October RCE fixes.
Keep in mind that every version you install is one more you have to keep patched. With Sury, at least, apt upgrade patches all of them at once.
The script
This is the complete script. It includes confirmations, uses a temporary directory that is removed on exit, and reads your answers from the terminal, so it works when you pipe it from curl. You can change the PHP version and the extensions with the variables at the top.
This script comes from my Neovim configuration repo, where I keep my install scripts. The editor needs php and composer for the PHP language servers and formatters to work, so I update them regularly. If you want the latest version, look there first.
#!/bin/bash
#
# Author: Ángel Blanco
# License: MIT
#
# This script is provided "as is", without warranty of any kind. Use at your own risk.
#
# Source: https://angelblanco.dev/blog/apt-install-php-85-on-ubuntu-26
#
# Usage:
# curl -fsSL https://angelblanco.dev/scripts/ubuntu-install-php-85.sh | bash
#
# Install PHP (from packages.sury.org) and Composer on Ubuntu.
# Normally you will call it from your non-root user; elevated permissions
# will be granted with sudo.
#
set -euo pipefail
# You can configure these variables if needed
PHP_VERSION="8.5"
PHP_EXTENSIONS="cli mbstring curl zip xml"
KEYRING="/usr/share/keyrings/debsuryorg-archive-keyring.gpg"
SOURCE_FILE="/etc/apt/sources.list.d/php.sources"
# Basic confirmation module before executing the script.
confirm_action() {
while true; do
read -r -p "$1 (y/n): " choice < /dev/tty
case "$choice" in
[Yy]|[Yy][Ee][Ss]) return 0 ;;
[Nn]|[Nn][Oo]) return 1 ;;
*) echo "Invalid choice. Please enter 'y' or 'n'." ;;
esac
done
}
WORKDIR="$(mktemp -d)"
trap 'rm -rf "$WORKDIR"' EXIT
if ! confirm_action "Install PHP $PHP_VERSION from packages.sury.org?"; then
echo "Skipping PHP installation. Exiting."
exit 0
fi
# --- Prerequisites ---------------------------------------------------------
sudo apt update
sudo apt install -y ca-certificates curl unzip
# --- Sury signing key (official keyring package) ---------------------------
curl -fsSLo "$WORKDIR/debsuryorg-archive-keyring.deb" https://packages.sury.org/debsuryorg-archive-keyring.deb
sudo dpkg -i "$WORKDIR/debsuryorg-archive-keyring.deb"
# --- Sury repository --------------------------------------------------------
CODENAME="$(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}")"
sudo tee "$SOURCE_FILE" > /dev/null <<EOF
Types: deb
URIs: https://packages.sury.org/php/
Suites: $CODENAME
Components: main
Architectures: $(dpkg --print-architecture)
Signed-By: $KEYRING
EOF
sudo apt update
# --- PHP --------------------------------------------------------------------
# Use -cli instead of the bare "phpX.Y" metapackage, which pulls in Apache.
PACKAGES=""
for EXTENSION in $PHP_EXTENSIONS; do
PACKAGES="$PACKAGES php${PHP_VERSION}-${EXTENSION}"
done
# shellcheck disable=SC2086
sudo apt install -y $PACKAGES
sudo update-alternatives --set php "/usr/bin/php${PHP_VERSION}"
echo ""
php --version
echo ""
# --- Composer ---------------------------------------------------------------
if confirm_action "Install Composer?"; then
cd "$WORKDIR"
EXPECTED_CHECKSUM="$(curl -fsSL https://composer.github.io/installer.sig)"
curl -fsSLo composer-setup.php https://getcomposer.org/installer
ACTUAL_CHECKSUM="$(php -r "echo hash_file('sha384', 'composer-setup.php');")"
if [ "$EXPECTED_CHECKSUM" != "$ACTUAL_CHECKSUM" ]; then
>&2 echo "ERROR: Invalid Composer installer checksum"
exit 1
fi
php composer-setup.php --quiet
sudo install -m 0755 composer.phar /usr/local/bin/composer
echo ""
composer --version
fi
echo ""
echo "Installation process finished."
echo "Run 'sudo apt update && sudo apt upgrade' periodically to get PHP security releases."
Keep everything up to date
Once it's installed, keeping it up to date takes just a couple of commands:
# Operating system and PHP
sudo apt update && sudo apt upgrade
# Composer
sudo composer self-update
Even on 8.5.12, php.net recommends disabling session upload progress until the next release, to fully close an unlikely RCE path (CVE-2026-115560). If you serve PHP with FPM, it's a one-liner:
echo 'session.upload_progress.enabled=0' | sudo tee /etc/php/8.5/fpm/conf.d/99-session-upload-progress.ini
sudo systemctl restart php8.5-fpm
And don't forget your project dependencies: composer outdated and composer audit tell you what needs updating and which packages have known vulnerabilities.
Two rounds of PHP security releases in less than a month, one of them an emergency, is a good reminder that you can't predict which patch will matter. What you can do is make applying them boring: one apt upgrade for PHP, one composer self-update for Composer, and composer audit for your projects. Set it up once and stop thinking about it.