Install PHP 8.5 with apt on Ubuntu 26.04

November 6, 2026 - by Ángel L. Blanco
On this page

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:

VersionReleasedActive support untilSecurity support until
8.2Dec 8, 2022Dec 31, 2024Dec 31, 2026
8.3Nov 23, 2023Dec 31, 2025Dec 31, 2027
8.4Nov 21, 2024Dec 31, 2026Dec 31, 2028
8.5Nov 20, 2025Dec 31, 2027Dec 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 for readonly classes 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() and array_last(). Finally.

Here's the pipe operator turning a title into a slug:

php
$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:

bash
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.

bash
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:

bash
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

bash
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

bash
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.

bash
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

bash
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:

bash
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:

bash
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:

bash
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:

bash
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.

ubuntu-install-php-85.sh
#!/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:

bash
# 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:

bash
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.