How to move a PHP website to a newer PHP version on Ubuntu or Debian

Install the new PHP next to the old one, test your site on it, then switch with one line. The old version stays installed, so going back takes a minute.

Don't remove the old version on the same day. Let the site run on the new one for a few days first. As long as the old version is installed, switching back is easy (step 9).

Before you start

  • Take a backup of the files and the database. See our backup guide.
  • Check that your app supports the new version: WordPress with its theme and plugins, or the Laravel, Symfony or Drupal version you use. Our post on which PHP versions still get security fixes helps you pick the target.
  • The examples move Ubuntu 24.04 from PHP 8.3 to 8.4, and Debian 13 from 8.4 to 8.5. Change the numbers to yours. The site is called shop and lives in /var/www/shop.

1. See what you run now

php -v

This shows the version of the php command. Your website may use a different one, so check the web server too. Nginx:

grep -Rn fastcgi_pass /etc/nginx/sites-enabled/

You'll see a socket such as /run/php/php8.3-fpm.sock (the default) or one of your own, such as /run/php/shop.sock. Apache:

ls /etc/apache2/conf-enabled/ /etc/apache2/mods-enabled/ | grep php

php8.3-fpm.conf means Apache uses PHP-FPM. php8.3.load means PHP runs inside Apache (mod_php).

2. Get the new version

Ubuntu 24.04 comes with PHP 8.3, Debian 13 with PHP 8.4 and Ubuntu 26.04 with PHP 8.5. You have two ways to get a newer one:

Know what you're adding. These packages are not from Ubuntu or Debian, but from a separate project. You trust one more source with software that runs as root, and it also updates the PHP version you already have: after adding it, apt takes your current PHP from there too. It may also list a version that isn't released yet. Pick one that php.net lists as supported.

Ubuntu (Launchpad PPA):

sudo apt install software-properties-common
sudo add-apt-repository ppa:ondrej/php
sudo apt update

Debian (deb.sury.org, the commands from its README):

sudo apt install lsb-release ca-certificates curl
sudo curl -sSLo /tmp/debsuryorg-archive-keyring.deb https://packages.sury.org/debsuryorg-archive-keyring.deb
sudo dpkg -i /tmp/debsuryorg-archive-keyring.deb
sudo sh -c 'echo "deb [signed-by=/usr/share/keyrings/debsuryorg-archive-keyring.gpg] https://packages.sury.org/php/ $(lsb_release -sc) main" > /etc/apt/sources.list.d/php.list'
sudo apt update

3. Install it next to the old one

List the PHP packages you have now:

dpkg -l | awk '/^ii/{print $2}' | grep php8.3

Install the same ones for the new version. This command swaps the version number in each name:

sudo apt install $(dpkg -l | awk '/^ii/{print $2}' | grep php8.3 | sed 's/8.3/8.4/')

Going to PHP 8.5? There is no php8.5-opcache package, because OPcache is built into PHP 8.5. Leave it out, or apt stops with "Unable to locate package":

sudo apt install $(dpkg -l | awk '/^ii/{print $2}' | grep php8.4 | grep -v opcache | sed 's/8.4/8.5/')

Now compare the extensions. Lines starting with < are only in the old version, so install those too:

diff <(php8.3 -m) <(php8.4 -m)

Check php -v again. On Ubuntu, installing 8.4 from the PPA moved the php command to 8.4 straight away, before your site moved. Cron jobs that call plain php then run on the new version. To keep the old one until you switch:

sudo update-alternatives --set php /usr/bin/php8.3

4. Copy your settings

The new version starts with default settings. If you put your changes in a file of your own, copy it:

sudo cp /etc/php/8.3/fpm/conf.d/99-custom.ini /etc/php/8.4/fpm/conf.d/

If you edited php.ini itself, compare the two files and make the same changes: diff /etc/php/8.3/fpm/php.ini /etc/php/8.4/fpm/php.ini. With mod_php, the folder is apache2 instead of fpm.

Your own PHP-FPM pools (see our guide to one pool per site) need a copy with a new socket name, because two pools can't share one socket:

sudo cp /etc/php/8.3/fpm/pool.d/shop.conf /etc/php/8.4/fpm/pool.d/shop.conf
sudo sed -i 's|/run/php/shop.sock|/run/php/shop-php8.4.sock|' /etc/php/8.4/fpm/pool.d/shop.conf
sudo php-fpm8.4 -t
sudo systemctl restart php8.4-fpm

Both versions now run side by side. ls /run/php/ shows the old and the new sockets.

5. Test before you switch

Syntax check every PHP file with the new version. Only problems are printed:

find /var/www/shop -name '*.php' -not -path '*/vendor/*' -print0 | xargs -0 -n1 php8.4 -l | grep -v '^No syntax errors'

This only finds code that no longer parses. It won't find code that runs but behaves differently, so read the official migration notes for 8.4 or 8.5.

Composer projects (Laravel, Symfony): check that the new version has every extension your packages need. Run it in the project folder, where composer.lock is:

cd /var/www/shop
php8.4 $(which composer) check-platform-reqs

Every line should end in success. A missing line names the extension to install, for example ext-gd means sudo apt install php8.4-gd.

Best of all: copy the site to a test address (for WordPress, see our staging copy guide), switch only the copy, and click through the important pages: login, forms, checkout and the admin area.

6. Switch the site

Nginx: in the site's file in /etc/nginx/sites-available/, point fastcgi_pass at the new socket, for example unix:/run/php/php8.4-fpm.sock or unix:/run/php/shop-php8.4.sock. Then:

sudo nginx -t && sudo systemctl reload nginx

Only this site moves. Other sites keep their own socket.

Apache with PHP-FPM:

sudo a2disconf php8.3-fpm
sudo a2enconf php8.4-fpm
sudo apache2ctl configtest && sudo systemctl reload apache2

This moves every site that uses the shared setting. If a site has its own SetHandler line with a socket, change the socket there instead.

Apache with mod_php: mod_php runs one PHP version for all sites. Install the new module first (the command in step 3 does that, because the list includes libapache2-mod-php8.3). Then:

sudo a2dismod php8.3
sudo a2enmod php8.4
sudo apache2ctl configtest && sudo systemctl restart apache2

Two warnings, both seen in our tests:

  • If a2enmod says the module doesn't exist, run sudo a2enmod php8.3 right away. Apache with no PHP module still passes its config test, but after the next restart it sends your PHP files as plain text, passwords included.
  • Use restart, not reload. When Apache swapped PHP modules without a full restart, it crashed.

7. Check which version runs the site

Don't leave a phpinfo() page lying around. Make a one-line file with a random name, open it once, and delete it:

echo '<?php echo PHP_VERSION;' | sudo tee /var/www/shop/v-8hq2x.php
curl https://example.com/v-8hq2x.php
sudo rm /var/www/shop/v-8hq2x.php

It should print the new version. Then watch the logs for an hour or so: sudo tail -f /var/log/nginx/error.log (or /var/log/apache2/error.log) and sudo tail -f /var/log/php8.4-fpm.log.

8. Move the php command, cron jobs and workers

Point the php command at the new version:

sudo update-alternatives --set php /usr/bin/php8.4
php -v

Then find jobs that name the old version directly:

sudo grep -rn 'php8.3' /etc/cron* /var/spool/cron /etc/systemd/system /etc/supervisor 2>/dev/null

Change each one to php8.4 (or plain php). Restart long-running workers so they pick up the new version, for example Laravel's queue workers with php artisan queue:restart.

9. Switch back if something breaks

Undo step 6: point Nginx at the old socket, or swap the a2enconf / a2enmod commands back, then reload (or restart for mod_php). The old version is still installed, so the site works again at once. Fix the problem on a test copy and try again.

10. Remove the old version

When the site has run well for a few days, check that nothing still uses the old version:

sudo grep -Rln 'php8.3' /etc/nginx/sites-enabled /etc/apache2/sites-enabled /etc/apache2/conf-enabled /etc/apache2/mods-enabled /etc/cron* /etc/systemd/system 2>/dev/null

If that prints nothing, keep a copy of the old settings and remove it:

sudo cp -a /etc/php/8.3 /root/php8.3-config-backup
sudo apt purge 'php8.3-*'
sudo apt autoremove

With mod_php, add libapache2-mod-php8.3 to the purge line. On Debian, apt may also offer to remove packages without a version number, such as php-fpm. That's fine: they only point at the default version, and your new php8.5-* packages stay.

If something goes wrong

  • Nginx shows "502 Bad Gateway": the socket name in fastcgi_pass is wrong, or the new PHP-FPM isn't running. Check with ls /run/php/ and systemctl status php8.4-fpm.
  • The browser shows PHP code as text: Apache has no PHP module turned on. Run sudo a2enmod php8.3 (or the new version, if it's installed) and restart Apache now.
  • "Call to undefined function", for example mb_strlen() or imagecreatetruecolor(): an extension is missing. Install it for the new version, such as php8.4-mbstring or php8.4-gd, and restart PHP-FPM.
  • A blank page or "500": the error is in the PHP-FPM log or the web server's error log from step 7.
  • Cron jobs still run the old version: they name php8.3 directly (step 8).

Official sources

Next steps: if you run several sites, give each one its own PHP-FPM pool, so you can move them one at a time. And work through the security checklist for a new PHP server.

ServerNest is being built to do this per site: install another PHP version next to the old one, switch one app at a time, check the site, and switch back on its own if the site breaks. Join early access to try it first.

← Back to the security blog