Ubuntu 22.04 ships PHP 8.1 in its own archive, so you can install PHP 8.1 on Ubuntu 22.04 with one apt command and no extra repository. PHP 8.1 reached upstream end of life on 31 December 2025, so fixes now come only from distro backports that keep the old version number, plus minimal source patches from PHP for people who build their own. Run 8.1 only because an existing application needs it, and start anything new on PHP 8.4 on Ubuntu or 8.5, because 8.2 reaches end of life on 31 December 2026.
You install PHP 8.1 and its extensions, then prove the build carries the PHP2Shell fix. Ubuntu 20.04 and 18.04 are no longer covered, because neither has a free patched PHP 8.1. Every command below except the reboot, the vim lines and the extension name template was run as a sudo user on a fresh Ubuntu 22.04 system in October 2026, including an upgrade from the pre-fix build.
Below is a summarised list of the key features new in the PHP 8.1 release:
- Readonly properties: Class properties can be marked as readonly hence can only be written once
- Support for DNS-over-HTTPS (DoH)
- AVIF Image Format support: The PHP 8.1βs image processing and GD extension adds support for AVIF images.
- Never Return Type: A new return type hint called never is added in PHP 8.1
- Added support for Fibers: The low level mechanism to manage concurrency
- PHP 8.1 adds array_is_list as a built-in function
- File Uploads with CURLStringFile: In PHP 8.1, PHP Curl extension now supports HTTP(S) requests with file uploads
- Enums: They are now added in PHP 8.1
- New fdatasync() and fsync() Functions
- New Sodium XChaCha20 functions
- Explicit Octal Numeral Notation
- Define final Class Constants
- MurmurHash3 hash algorithm support
- Intersection Types
- xxHash hash algorithms support
- For detailed information read through the official PHP 8.1 release page
Update the Ubuntu system
Start the PHP 8.1 installation on Ubuntu 22.04 by making sure the system is updated and upgraded, so the PHP packages land on current libraries.
sudo apt update && sudo apt -y upgrade
Reboot when the upgrade pulled in a new kernel or core libraries such as glibc, because running services keep the old copies in memory until they restart. Ubuntu flags that case with /var/run/reboot-required, so this line reboots only when the file exists and does nothing otherwise.
[ -f /var/run/reboot-required ] && sudo systemctl reboot
If it rebooted, log in again once the server is back and carry on with the package source check.
Check the PHP 8.1 package source
Ubuntu 22.04 has PHP 8.1 and its extensions in its own repositories, and Ubuntu’s security team backports fixes into them, so the OndΕej SurΓ½ PPA is not needed on 22.04. Confirm which builds apt can see before you install anything, because a server that misses the security pocket installs the original, vulnerable release build without any warning.
apt-cache madison php8.1-common
Three lines come back. The original release build sits in jammy, and the current security build is in jammy-security and jammy-updates. Apt installs the highest one, which is the build that carries the PHP2Shell fix.
php8.1-common | 8.1.2-1ubuntu2.28 | http://archive.ubuntu.com/ubuntu jammy-updates/main amd64 Packages
php8.1-common | 8.1.2-1ubuntu2.28 | http://security.ubuntu.com/ubuntu jammy-security/main amd64 Packages
php8.1-common | 8.1.2-1ubuntu2 | http://archive.ubuntu.com/ubuntu jammy/main amd64 Packages
If your list stops at the plain jammy line, the server isn’t reading the security pocket, so fix /etc/apt/sources.list before going further. If you added ppa:ondrej/php on this server earlier, apt takes PHP 8.1 from the PPA instead, and its fixed build is 8.1.34-9. The changelog check in the PHP2Shell section works for both sources.
Ubuntu 20.04 and 18.04 are a different story, because no patched PHP 8.1 package exists for either. The PPA’s focal and bionic indexes are empty, and Ubuntu’s own PHP there is php7.4 on 20.04 and php7.2 on 18.04, whose PHP2Shell fixes (7.4.3-4ubuntu2.29+esm5 and 7.2.24-0ubuntu0.18.04.17+esm14) ship only through Ubuntu Pro. There is no upstream 8.1 release either, only PHP’s minimal source patches for people who maintain their own builds. Without Pro, upgrade the server to 22.04 or 24.04 and move the application to the current PHP 8.5 release on Ubuntu 24.04.
Install PHP 8.1
The php8.1 metapackage installs the CLI and the Apache module, and it pulls in Apache itself. Apache is enabled and started on port 80 straight away, serving PHP through mod_php, so if you planned Nginx on this host, stop and disable Apache before Nginx needs the port. Install the metapackage.
sudo apt install php8.1
Hit the y key to start installation. The package list shows Apache and libapache2-mod-php8.1 coming along.
The following additional packages will be installed:
apache2 apache2-bin apache2-data apache2-utils bzip2 file
libapache2-mod-php8.1 libapr1 libaprutil1 libaprutil1-dbd-sqlite3
libaprutil1-ldap libbsd0 libedit2 libgdbm-compat4 libgdbm6 libjansson4
liblua5.3-0 libmagic-mgc libmagic1 libmd0 libperl5.34 libsodium23 mailcap
mime-support netbase perl perl-modules-5.34 php-common php8.1-cli
php8.1-common php8.1-opcache php8.1-readline psmisc ssl-cert ucf xz-utils
Suggested packages:
apache2-doc apache2-suexec-pristine | apache2-suexec-custom www-browser ufw
bzip2-doc php-pear gdbm-l10n perl-doc libterm-readline-gnu-perl
| libterm-readline-perl-perl make libtap-harness-archive-perl
The following NEW packages will be installed:
apache2 apache2-bin apache2-data apache2-utils bzip2 file
libapache2-mod-php8.1 libapr1 libaprutil1 libaprutil1-dbd-sqlite3
libaprutil1-ldap libbsd0 libedit2 libgdbm-compat4 libgdbm6 libjansson4
liblua5.3-0 libmagic-mgc libmagic1 libmd0 libperl5.34 libsodium23 mailcap
mime-support netbase perl perl-modules-5.34 php-common php8.1 php8.1-cli
php8.1-common php8.1-opcache php8.1-readline psmisc ssl-cert ucf xz-utils
0 upgraded, 37 newly installed, 0 to remove and 0 not upgraded.
Need to get 16.3 MB of archives.
After this operation, 87.7 MB of additional disk space will be used.
Do you want to continue? [Y/n] y
Check which PHP build the CLI runs. On Ubuntu this line tells you more than the upstream version, so read all of it.
php --version
The upstream number stays at 8.1.2 for the whole life of Ubuntu 22.04. The part after it is the Ubuntu package revision, and that is what moves with every security update.
PHP 8.1.2-1ubuntu2.28 (cli) (built: Oct 19 2026 18:54:45) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.1.2, Copyright (c) Zend Technologies
with Zend OPcache v8.1.2-1ubuntu2.28, Copyright (c), by Zend Technologies
The build date alone proves nothing about security fixes, so use the checks in the next section rather than reading dates.
Check PHP 8.1 for PHP2Shell (CVE-2026-109618, CVE-2026-109619)
PHP2Shell is a pair of critical remote code execution bugs in PHP itself, CVE-2026-109618 and CVE-2026-109619, scored CVSS v4.0 9.2 in php.net’s 20 October pre-announcement. PHP fixed them upstream on 21 October 2026 in 8.5.12, 8.4.27, 8.3.36 and 8.2.35, with no release for end-of-life 8.1. Ubuntu backported the fix into 8.1.2-1ubuntu2.28 in jammy-security, so a fresh install from this guide is already patched. The PHP security advisory has the details.
The package changelog is the reliable check, because it names the CVEs the build fixes. You can run it on any server that already has PHP 8.1 from Ubuntu or the PPA.
zgrep -E 'CVE-2026-10961[89]' /usr/share/doc/php8.1-common/changelog.Debian.gz
A patched build prints these three lines. No output means the installed package is older than 8.1.2-1ubuntu2.28.
- debian/patches/CVE-2026-109618_109619.patch: Avoid state corruption via
- CVE-2026-109618
- CVE-2026-109619
Minimized Ubuntu images strip /usr/share/doc, and then the changelog is not there to read. This version comparison works without it, but only for Ubuntu’s own packages: a PPA build such as 8.1.34 always compares higher than 8.1.2 and reads as patched even when it is not. On a PPA server, put 8.1.34-9 in place of 8.1.2-1ubuntu2.28.
dpkg --compare-versions "$(dpkg-query -W -f='${Version}' php8.1-common)" ge 8.1.2-1ubuntu2.28 && echo patched || echo VULNERABLE
It printed VULNERABLE on our test server while the original release build was installed, and this after the upgrade.
patched
A fresh install can stop here and move on to the extensions. On an existing server where either check fails, upgrade every PHP 8.1 package. The Apache module and the embed library start with lib, so the glob misses them and they’re named explicitly. APT skips any name that isn’t installed:
sudo apt update
sudo apt install -y --only-upgrade 'php8.1*' libapache2-mod-php8.1 libphp8.1-embed
When we ran the upgrade, the packages restarted Apache and PHP-FPM on their own. Restart them yourself anyway so no worker keeps the old code in memory. A mod_php server restarts Apache, and a server that also has php8.1-fpm (the extensions step installs it) restarts both:
sudo systemctl restart apache2 php8.1-fpm
Drop php8.1-fpm from that line if it is not installed, or drop apache2 on an Nginx server. Long-running PHP CLI processes, such as queue workers kept alive by supervisor or a systemd unit, hold the old code too, so restart those as well. Next, find when the PHP 8.1 packages were last upgraded, including entries in rotated logs.
zgrep -hE ' (install|upgrade) php8.1-(common|fpm):' /var/log/dpkg.log* | sort | tail -1
Our test server shows the jump from the release build to the fixed one.
2026-10-22 16:04:15 upgrade php8.1-common:amd64 8.1.2-1ubuntu2 8.1.2-1ubuntu2.28
Compare that with the moment each restarted service came up. A service that started before the upgrade is still running the old code, whatever the package database says.
systemctl show -p ActiveEnterTimestamp apache2 php8.1-fpm
Both timestamps must be later than the dpkg line, and here they are.
ActiveEnterTimestamp=Thu 2026-10-22 16:05:04 UTC
ActiveEnterTimestamp=Thu 2026-10-22 16:05:04 UTC
For the last check, ask the running web server itself. Ubuntu’s patched build reports its package revision in PHP_VERSION, so a small test page tells you which code the worker is executing. The command below writes it to /var/www/html, the docroot of Apache’s default site; on your server, use the docroot of the site you actually serve, since Ubuntu’s default Nginx site has PHP handling commented out.
echo '<?php echo PHP_VERSION, " ", PHP_SAPI, PHP_EOL;' | sudo tee /var/www/html/info.php
curl -s http://127.0.0.1/info.php
Before the upgrade, this page on our server printed a bare 8.1.2. After the upgrade and restart, Apache with mod_php answered with the fixed revision and apache2handler, while an Nginx site using PHP-FPM reports fpm-fcgi in the second field:
8.1.2-1ubuntu2.28 apache2handler
Anything other than 8.1.2-1ubuntu2.28 or later means a worker is still on old code, or PHP comes from a different source. Remove the test page once you’ve read it, since it tells anyone which PHP build you run:
sudo rm /var/www/html/info.php
With the fixed build confirmed, add the extensions your application needs.
Install PHP 8.1 extensions
Every PHP 8.1 extension on Ubuntu 22.04 is a separate package named php8.1- followed by the extension, for example php8.1-mysql, php8.1-zip or php8.1-xml. The general form is:
sudo apt install php8.1-<extension>
Names differ between Ubuntu’s archive and third-party repositories, so list the extension packages your configured sources actually offer before you build an install line.
apt-cache pkgnames php8.1- | sort | paste - - - - -
Ubuntu’s archive carries these on 22.04.
php8.1-amqp php8.1-apcu php8.1-ast php8.1-bcmath php8.1-bz2
php8.1-cgi php8.1-cli php8.1-common php8.1-curl php8.1-dba
php8.1-dev php8.1-ds php8.1-enchant php8.1-fpm php8.1-gd
php8.1-gearman php8.1-gmagick php8.1-gmp php8.1-gnupg php8.1-http
php8.1-igbinary php8.1-imagick php8.1-imap php8.1-interbase php8.1-intl
php8.1-ldap php8.1-mailparse php8.1-mbstring php8.1-memcache php8.1-memcached
php8.1-mongodb php8.1-msgpack php8.1-mysql php8.1-oauth php8.1-odbc
php8.1-opcache php8.1-pcov php8.1-pgsql php8.1-phpdbg php8.1-ps
php8.1-pspell php8.1-psr php8.1-raphf php8.1-readline php8.1-redis
php8.1-rrd php8.1-smbclient php8.1-snmp php8.1-soap php8.1-solr
php8.1-sqlite3 php8.1-ssh2 php8.1-sybase php8.1-tidy php8.1-uopz
php8.1-uploadprogress php8.1-uuid php8.1-xdebug php8.1-xml php8.1-xmlrpc
php8.1-xsl php8.1-yac php8.1-yaml php8.1-zip php8.1-zmq
The line below installs the set most PHP applications ask for: drivers for MySQL and PostgreSQL, XML, mbstring, intl, GD, zip and curl, plus FPM and CGI. Bash expands the braces into sixteen package names, so you type it once. Trim it to what your application lists in its requirements, because every extra module is code loaded into each PHP worker.
sudo apt install php8.1-{bcmath,xml,fpm,mysql,zip,intl,ldap,gd,cli,bz2,curl,mbstring,pgsql,opcache,soap,cgi}
The FPM package prints a notice that it isn’t enabling itself in Apache. That is expected. Apache keeps serving PHP through mod_php, and PHP-FPM runs alongside it until you point a web server at its socket, so nothing changes for your site at this point. To list all PHP 8.1 loaded modules, run the following command.
php --modules
You should see the modules from the extension packages next to the ones built into PHP:
[PHP Modules]
bcmath
bz2
calendar
Core
ctype
curl
date
dom
exif
FFI
fileinfo
filter
ftp
gd
gettext
hash
iconv
intl
json
ldap
libxml
mbstring
mysqli
mysqlnd
openssl
pcntl
pcre
PDO
pdo_mysql
pdo_pgsql
pgsql
Phar
posix
readline
Reflection
session
shmop
SimpleXML
soap
sockets
sodium
SPL
standard
sysvmsg
sysvsem
sysvshm
tokenizer
xml
xmlreader
xmlwriter
xsl
Zend OPcache
zip
zlib
[Zend Modules]
Zend OPcache
Nginx has no PHP module, so with Nginx every PHP request goes to PHP-FPM, and the service has to be running before the site works. Check it now, because a failed start here only shows up later as a 502 error from Nginx, which points you at the wrong component.
systemctl status php*-fpm.service
The package enables and starts the service on install, so it should read active (running):
β php8.1-fpm.service - The PHP 8.1 FastCGI Process Manager
Loaded: loaded (/lib/systemd/system/php8.1-fpm.service; enabled; vendor preset: enabled)
Active: active (running) since Thu 2026-10-22 16:02:01 UTC; 5s ago
Docs: man:php-fpm8.1(8)
Process: 17030 ExecStartPost=/usr/lib/php/php-fpm-socket-helper install /run/php/php-fpm.sock /etc/php/8.1/fpm/pool.d/www.conf 81 (code=exited, status=0/SUCCESS)
Main PID: 17027 (php-fpm8.1)
Status: "Ready to handle connections"
Tasks: 3 (limit: 1204)
Memory: 10.6M
CPU: 66ms
The ExecStartPost line registers the versionless /run/php/php-fpm.sock path next to the pool socket. PHP-FPM’s default configuration files, which set the listening socket, the user and other pool settings, are located in:
ls -1 /etc/php/8.1/fpm/
The directory holds the main config, its own php.ini, and the pool and extension directories:
conf.d
php-fpm.conf
php.ini
pool.d
Pool settings such as the socket and the worker user live in www.conf, and the master process settings in php-fpm.conf. Open either with:
sudo vim /etc/php/8.1/fpm/pool.d/www.conf
sudo vim /etc/php/8.1/fpm/php-fpm.conf
Reload the service with sudo systemctl reload php8.1-fpm after any change, so the workers pick it up.
That’s PHP 8.1 installed on Ubuntu 22.04 from Ubuntu’s own archive, patched against PHP2Shell, with the common extensions and PHP-FPM ready. Keep jammy-security in your sources so future backports arrive with apt upgrade, and plan the move to a supported branch while 8.1 still gets them. On Debian, the matching guide is PHP 8.1 on Debian 13 and 12.
If 8.1 is only a stopgap, the PHP 8.2 guide for 24.04 and 22.04 covers the smallest jump, though 8.2 itself ends support on 31 December 2026, and the PHP 8.5 setup on Ubuntu 26.04 is where a new server should end up.
Doesn’t work. Ubuntu 20.04
After install:
$ sudo apt install php8.0
Reading package lists… Done
Building dependency tree
Reading state information… Done
E: Unable to locate package php8.0
E: Couldn’t find any package by glob ‘php8.0’
Please check of repo is configured correctly.
If this is anymore helpful, I won’t even need a brain ^_^
Worked like a champ (April 2022)
Thank you for the clear instructions and easy to follow.
Thanks, sir.
It has worked well for me!
Well this works well…
awesome
thanks for this post very helpful,
I have a question when i run php –module , i am getting this warning :
PHP Warning: PHP Startup: Unable to load dynamic library ‘zip.so’ (tried: /usr/lib/php/20210902/zip.so (/usr/lib/php/20210902/zip.so: undefined symbol: zip_register_cancel_callback_with_state), /usr/lib/php/20210902/zip.so.so (/usr/lib/php/20210902/zip.so.so: cannot open shared object file: No such file or directory)) in Unknown on line 0
and the zip extension doesn’t appear in list of installed modules
Try creating the following script and check if zip is loaded.
phpinfo();
Hallo, today (since June 2023) the Ondrej Sury PPAs are not anymore available for Ubuntu 18.04 !!!
Its EOL so the PPA closed π
I didnt find any backup, they just vanished!
I wanted to install php8.1-mbstring, and noticed that there is nothing in the sources, so first tried to uninstall all php8.1 with the result, that now all is missing, not only mbstring π π
I am still on 8.0 though, and 8.2 I also still have, but 8.1 is gone.
Any clue how to get the ppa back, maybe from wayback?
Thanks for hints.
franks
Please use https://computingforgeeks.com/install-php-8-7-on-ubuntu-18-bionic-beaver/