Old PHP versions stop getting security fixes, and new plugins and frameworks drop support for them. Upgrading does not have to be risky. Because you can install the new PHP-FPM next to the old one, you switch each site over with a one-line config change and can switch back just as fast. This guide uses PHP 8.1 to 8.3 as the example. The steps are the same for any pair of versions.
Before you start
- A Linux VPS running Ubuntu 22.04/24.04 or Debian 12 with a sudo user, serving PHP through PHP-FPM.
- The repository that provides the new version. On most servers that means Ondřej Surý's packages: Install multiple PHP versions on Ubuntu and Debian. On AlmaLinux or Rocky Linux, see the Remi section near the end.
- A backup of your site files and database, taken today.
- Confirmation that your apps support the new version. Check the release notes of your CMS, framework and important plugins.
Warning: A major PHP upgrade can break old code that uses removed functions. Always test on a staging copy or at least on one site first, and keep the old version installed until you are sure.
Record what you use now
List the installed packages for the current version:
dpkg -l | awk '/^ii/ && /php8.1/ {print $2}'You will see names like php8.1-fpm, php8.1-mysql, php8.1-curl. You need the same set for the new version.
Also list the modules PHP has loaded, for comparison later:
php8.1 -m > ~/php81-modules.txtFind any settings you changed from the defaults. Look for files you added to conf.d, such as 99-custom.ini, and print the active lines of your pool files:
ls -l /etc/php/8.1/fpm/conf.d/
grep -vE '^\s*(;|$)' /etc/php/8.1/fpm/pool.d/*.confIf you edited php.ini directly, compare it with the package default to see your changes:
diff <(grep -vE '^\s*(;|$)' /usr/lib/php/8.1/php.ini-production) <(grep -vE '^\s*(;|$)' /etc/php/8.1/fpm/php.ini)Install the new version and extensions
Build the install command from your list by swapping the version number:
dpkg -l | awk '/^ii/ && /php8.1-/ {print $2}' | sed 's/8\.1/8.3/' | xargs sudo apt install -yReview the list apt shows before it installs. If a package does not exist for the new version, apt stops and names it. Some extensions were merged into PHP itself or renamed, so look up any that fail and drop or replace them.
Compare the modules:
php8.3 -m > ~/php83-modules.txt
diff ~/php81-modules.txt ~/php83-modules.txtAny line starting with < is a module you had before and do not have now.
Carry over your settings
Do not copy the old php.ini over the new one. Defaults change between versions. Instead, put your custom values in a small override file for the new version:
sudo nano /etc/php/8.3/fpm/conf.d/99-custom.inimemory_limit = 256M
upload_max_filesize = 64M
post_max_size = 64M
max_execution_time = 120If you created custom pools (one per site), copy each pool file across and change the socket path inside it:
sudo cp /etc/php/8.1/fpm/pool.d/example.conf /etc/php/8.3/fpm/pool.d/
sudo sed -i 's/php8\.1-fpm/php8.3-fpm/g' /etc/php/8.3/fpm/pool.d/example.confCheck the new config and start the service:
sudo php-fpm8.3 -t
sudo systemctl enable --now php8.3-fpmtest is successful means the configuration parsed cleanly.
Switch the web server
Nginx
In each site's server block, change the socket in fastcgi_pass:
fastcgi_pass unix:/run/php/php8.3-fpm.sock;Then test and reload:
sudo nginx -t && sudo systemctl reload nginxApache with PHP-FPM
If the site uses the global php8.1-fpm conf, swap it:
sudo a2disconf php8.1-fpm
sudo a2enconf php8.3-fpm
sudo apachectl configtest && sudo systemctl reload apache2If you set the handler per virtual host, change the socket path in that site's SetHandler line instead, then run the same test and reload.
Command line and cron
Point the php command at the new version, and update any cron jobs or scripts that call php8.1 directly:
sudo update-alternatives --set php /usr/bin/php8.3
crontab -l | grep phpTest, then keep a way back
- Load the home page, a few inner pages, the admin area, a form and any checkout.
- Watch the PHP-FPM log for new warnings while you click around:
sudo tail -f /var/log/php8.3-fpm.log- Check your app's own error log too, such as
wp-content/debug.logfor WordPress.
If something breaks, change the socket back to php8.1-fpm.sock and reload. The old version is still running, so the rollback takes seconds.
Remove the old version
After a few days without problems, remove the old packages to free memory and stop patching them:
sudo systemctl disable --now php8.1-fpm
sudo apt purge 'php8.1*'
sudo apt autoremoveWarning: apt purge deletes the old version's configuration in /etc/php/8.1. Copy anything you might still want first.
AlmaLinux and Rocky Linux with Remi
With Remi module streams, the default version is replaced in place. Switch the stream, then sync packages:
sudo dnf module switch-to -y php:remi-8.3
sudo systemctl restart php-fpmBecause this replaces the old version, test it on a staging server first, or install the new version alongside as php83-php-fpm and move sites one at a time. See Install PHP 8.x on AlmaLinux and Rocky Linux with Remi.
Check it works
php -vshows the new version.- A temporary page with
<?php echo PHP_VERSION;shows the new version through the web server. Delete it afterwards. systemctl status php8.3-fpmshows active (running).
Troubleshooting
- 502 Bad Gateway after switching: the new FPM service is not running, or the socket path has a typo. Check
ls /run/php/. - "Call to undefined function" errors: an extension is missing for the new version, such as
mysqliorgd. Install thephp8.3-package and restart PHP-FPM. - Deprecation warnings flood the page: set
display_errors = Offin production and update the code or plugin. - Uploads fail after the upgrade: your old upload limits were not carried over. Add them to
99-custom.ini.
Next steps
- Turn on opcode caching for the new version: Tune PHP OPcache.
- Run different sites on different versions during the move: Use a different PHP version per website in Nginx.
