Linux Basics

Upgrading from PHP 8.2 to 8.4: a practical checklist for code, extensions and servers

A step-by-step PHP 8.2 to 8.4 checklist: what changed in 8.3 and 8.4, removed extensions, deprecations, testing tools and a safe switch on Debian or Ubuntu.

Published
Reading time
7 min read

PHP 8.2 reaches end of life on 31 December 2026, and PHP 8.4 is the natural next stop: it has security support until 31 December 2028 and is the default PHP in Debian 13. Going from 8.2 to 8.4 crosses two sets of changes, those of 8.3 and those of 8.4, so it pays to work through them in order.

This checklist is built from the two official migration guides and adds the testing and server steps around them.

1. Prepare

  • Inventory each application: framework or CMS version, PHP extensions used (php8.2 -m), cron jobs and queue workers that run PHP.
  • Check the framework first. Upgrade to a framework or CMS release that supports PHP 8.4 before touching PHP itself.
  • Check Composer constraints. This lists the packages whose requirements block PHP 8.4:
    composer prohibits php 8.4
  • Build a staging copy with the same data shape as production. Never do the first run on the live server.

2. Changes from 8.2 to 8.3

Backward-incompatible changes worth checking:

  • The Date extension now throws its own DateError and DateException subclasses instead of warnings and generic exceptions in many cases. Review try/catch blocks around date parsing.
  • range() is stricter: it throws on objects, resources or arrays as bounds, on a step of 0, on a negative step for increasing ranges, and on infinite or NAN arguments.
  • Programs close to overflowing the call stack may now throw an Error, controlled by the new zend.max_allowed_stack_size setting.
  • Assigning a negative index $n to an empty array now makes the next index $n+1 instead of 0.
  • The opcache.consistency_checks directive was removed.

Deprecations introduced in 8.3:

  • Calling get_class() or get_parent_class() without arguments.
  • Using ++ on empty or non-alphanumeric strings (and, softly, on non-numeric strings); use str_increment().
  • assert_options(), the ASSERT_* constants and the assert.* INI settings.
  • ldap_connect() with separate host and port, a negative width in mb_strimwidth(), and ReflectionProperty::setValue() with a single argument.

3. Changes from 8.3 to 8.4

Backward-incompatible changes worth checking:

  • Extensions removed from core: IMAP, OCI8, PDO_OCI and PSpell moved to PECL. Check whether your platform still packages them (see step 5).
  • exit() and die() now behave more like functions: they follow strict_types and normal type coercion.
  • The E_STRICT error level was removed (and the constant deprecated).
  • Several resources became objects (DBA, ODBC, parts of SOAP); replace is_resource() checks as the guide describes.
  • Many functions now throw ValueError on invalid arguments, including round() with an invalid mode, str_getcsv() with multi-byte separators, and GD image functions with an invalid quality.
  • The bundled PCRE2 is 10.44, so {,3} is now a quantifier rather than literal text.
  • JIT defaults changed to opcache.jit=disable with opcache.jit_buffer_size=64M. JIT is still off by default, but if you enabled it by setting only the buffer size, check that it is really on.
  • For MySQL 8.0.24 and later, mysqlnd reports server wait timeouts as error 4031 instead of 2006. Reconnect logic that matches on 2006 needs updating.

Deprecations introduced in 8.4, in rough order of how often they show up:

  • Implicitly nullable parameters. function f(Foo $x = null) must become function f(?Foo $x = null). This is usually the largest batch of messages.
  • trigger_error() with E_USER_ERROR; throw an exception or call exit() instead.
  • Relying on the default escape parameter of fputcsv(), fgetcsv() and str_getcsv(); pass it explicitly.
  • mysqli_ping(), mysqli_kill() and mysqli_refresh(); lcg_value(); session_set_save_handler() with more than two arguments; changing session.sid_length or session.sid_bits_per_character.
  • Raising zero to a negative power, and naming a class _.

4. Test

  • Lint with 8.4. Since PHP 8.3, php -l accepts several files at once. It finds syntax errors, not runtime problems.
    find . -path ./vendor -prune -o -name '*.php' -print0 | xargs -0 -n 200 php8.4 -l | grep -v '^No syntax errors'
  • Static analysis. PHPStan can analyse against a target version with its phpVersion parameter (for 8.4, phpVersion: 80400). The PHPCompatibility standard for PHP_CodeSniffer checks code against a testVersion, for example --runtime-set testVersion 8.4; how far its checks reach depends on the version you install.
  • Automated fixes. Rector can apply many upgrade changes, such as making implicit nullable types explicit. Configure its PHP sets with ->withPhpSets() in rector.php, preview with vendor/bin/rector process --dry-run, and review the diff like any other change.
  • Run the test suite and click through the application on 8.4 with deprecations visible: error_reporting = E_ALL, log_errors = On, display_errors = Off. Read the log afterwards.
  • Check the platform: on the target server, composer check-platform-reqs confirms that the real PHP version and extensions satisfy the installed packages.

5. Get PHP 8.4 on the server

  • Debian 13: PHP 8.4 is the distribution default; install php8.4-fpm and the extension packages you need.
  • Debian 12: ships 8.2. Either move to Debian 13 or use a third-party repository for 8.4, pinned so it does not replace other packages unexpectedly.
  • Ubuntu 24.04 LTS: ships 8.3. PHP 8.4 requires a third-party repository.
  • Ubuntu 26.04 LTS: ships 8.5, not 8.4. On that release, target 8.5.

Watch the removed extensions: Debian 13 has no IMAP extension package for PHP 8.4 in its main suites, so applications that call imap_* functions (webmail clients, helpdesk mail fetchers) need another solution before you move. php8.4-pspell is packaged in Debian 13 from the PECL extension.

sudo apt install php8.4-fpm php8.4-cli php8.4-mysql php8.4-xml php8.4-mbstring php8.4-curl php8.4-intl php8.4-zip php8.4-gd
php8.2 -m > /tmp/mods-82.txt; php8.4 -m > /tmp/mods-84.txt; diff /tmp/mods-82.txt /tmp/mods-84.txt

Copy only your own settings: pool files in /etc/php/8.4/fpm/pool.d/ and small overrides in conf.d/. Do not copy a whole 8.2 php.ini.

6. Switch and keep a way back

# Nginx
fastcgi_pass unix:/run/php/php8.4-fpm.sock;
sudo nginx -t && sudo systemctl reload nginx

# Apache with PHP-FPM
sudo a2disconf php8.2-fpm && sudo a2enconf php8.4-fpm
sudo apachectl configtest && sudo systemctl reload apache2

# CLI used by cron and workers
php -v
sudo update-alternatives --config php

Leave php8.2-fpm running until the new version has handled real traffic for a while; rolling back is then a one-line change and a reload. Restart queue workers and schedulers so they pick up the new binary. When everything is stable, remove the 8.2 packages.

What you should do

  • Upgrade the framework and clear blockers reported by composer prohibits php 8.4.
  • Fix implicit nullable parameters and the other 8.3 and 8.4 deprecations, using Rector and static analysis to speed it up.
  • Confirm that every extension you need, especially IMAP, is available for 8.4 on your platform.
  • Test on a staging copy with full error logging, then switch the FPM socket with the old version kept ready.
  • Finish before 31 December 2026, when PHP 8.2 stops receiving upstream security fixes.

Sources

SKYLINE Engineering

@skyline

The engineering team at SKYLINE Industrial Solutions. We publish field-tested guides drawn from real KSA and GCC deployments.

See author profile
SKYLINE engineering services

Need this implemented for you?

Reading is free — building it right takes a team. SKYLINE engineers ship Linux Basics for Aramco vendors, banks, hospitals and government agencies across Saudi Arabia. Talk to us before you start.

Aramco Approved Contractor ISO 9001 · ISO 27001 SAMA CSF aligned NCA ECC ready 247+ KSA clients

Comments

0 total · 0 threads
Be the first to leave a comment.