Upgrade guide: PHP 8.1 to 8.3

PHP 8.1 to 8.3: the shortest way off an unsupported branch

PHP 8.1 lost security support on 31 December 2025 and has no PHP2Shell release, so securing the server and moving off it are both urgent. Going to 8.3 crosses just two releases, and the code work is mostly about undeclared properties, string handling and stricter functions. Secure the 8.1 server first, then test on 8.3.36.

Last reviewed against php.net's 8.2 and 8.3 migration guides and the PHP2Shell advisories.

Fix first, then migrate

Get a patched 8.1 build in place, then start the move

8.1's support ran out at the end of 2025, less than ten months before the security releases of 21 October 2026. Even a two-release migration needs a test cycle, so the server needs protecting in the meantime.

  • Distribution or host packages. Some suppliers have patched their 8.1 builds. Ubuntu's USN-8948-1 covers php8.1 on 22.04, and hosting control panels have shipped backports too. Look up both CVE numbers in your supplier's security notices.
  • Compiling from source. The PHP-8.1-CVE-2026-109618-109619 branch in php-src carries the PHP security team's minimal backport.
  • Nothing available yet. For servers on 8.1 or older, CISA says to take them offline.

The route in brief

From PHP 8.1: security support ended 31 December 2025; final release 8.1.34
PHP2Shell on 8.1 No release from the PHP project; source backport and supplier packages only
To PHP 8.3, security fixes until 31 December 2027
Build to install 8.3.36 or later
Releases to absorb Two: 8.2 and 8.3

Not certain the server is really on 8.1? Check the PHP version first. The upgrade hub covers other starting points.

Choosing the target

When 8.3 is the right place to stop

3D padlock beside a rising chart, for platforms that top out at 8.3

Your software stops at 8.3

Some platform releases list 8.3 as their highest PHP version, Laravel 10 and Magento 2.4.7 among them. Then the platform has made the decision for you.

3D watering can and growing plants, for a smaller set of changes

Less to change

Two releases instead of three, and no 8.4 extension removals. When the 8.1 server is exposed, a smaller change set reaches production sooner.

3D planner with a clock and calendar, for support until 2027

Enough runway to plan properly

8.3 gets security fixes until 31 December 2027: time to upgrade the application itself, then move PHP again without the pressure you are under now.

3D signpost and paper plane, for the next move after 8.3

The next hop is already mapped

Clearing 8.4's deprecations while you are on 8.3 makes the following move a small one. The list is further down this page.

If none of these applies and your platform supports it, the 8.1 to 8.4 route gives a year more support for one more release of changes.

Before you start

The 8.1 deprecations you are probably still carrying

If the site has run on 8.1 for a while, its log may already contain 8.1's own notices. Moving to 8.3 leaves their status unchanged, which makes them easy to ignore and worth clearing now.

  • Null passed to built-in functions. Deprecated since 8.1 and still a deprecation on 8.3, so these notices neither disappear nor turn into errors by moving.
  • Missing return types on overridden internal methods. Silenced with #[\ReturnTypeWillChange] if you added it during 8.1; php.net says that attribute stops working once strict checking arrives.
  • FILTER_SANITIZE_STRING, strftime() and mhash(). Still deprecated, still working, still worth replacing while you are in the code.

First release: 8.2

PHP 8.2: what changed, and where it hides in 8.1 code

Most 8.2 changes surface as deprecation notices rather than errors. One trigger that turns up in older code looks like this:

<?php
class Customer
{
    public int $id;
    public string $email;

    public function __construct(array $row)
    {
        foreach ($row as $key => $value) {
            // PHP 8.2 and later: deprecated for any key not declared above
            $this->$key = $value;
        }
    }
}

Undeclared properties log a deprecation

What changed: From 8.2, assigning $object->name when name is not declared on the class is deprecated. There are three ways out: declare the property, mark the class with #[\AllowDynamicProperties] (children inherit it), or hold side data in a WeakMap.

What to look for: Classes that fill themselves from arrays in a loop, as in the example above, and test doubles that bolt on fields.

Two string and callable forms retire

What changed: Interpolating with "${var}" or "${expr}" is deprecated. So are callables that only call_user_func() ever accepted, such as "static::method" and ["self", "method"].

What to look for: Plain "$var" or "{$var}" replaces the first; first-class callable syntax like $this->method(...) is an alternative to the second.

Case mapping is ASCII only

What changed: strtolower(), strtoupper(), ucfirst(), lcfirst(), ucwords() and the case-insensitive search functions no longer consult the locale.

What to look for: Applications that set a locale such as Turkish or German and rely on these functions for non-ASCII letters; switch those calls to mb_strtolower() and friends.

Latin-1 helpers deprecated

What changed: utf8_encode() and utf8_decode() now raise deprecation notices.

What to look for: Feeds and imports from older systems. The manual points to mb_convert_encoding(), UConverter::transcode() or iconv(), which also cope with Windows-1252.

Return values that changed

What changed: str_split('') and glob() under a fully blocking open_basedir now return empty arrays. strcmp() and its relatives may return -1 or 1 rather than the length difference, and random_bytes() and random_int() throw Random\RandomException on failure.

What to look for: Code that compares strcmp() results with specific numbers instead of with 0, and checks against false where an array now arrives.

A default time zone

What changed: If date.timezone is not set, the default time zone now falls back to UTC.

What to look for: Servers that never set date.timezone. Set it explicitly so dates do not depend on this fallback.

Sources on php.net: 8.2 incompatible changes, 8.2 deprecations, 8.2 other changes.

Second release: 8.3

PHP 8.3: exceptions, stricter inputs and a few deprecations

The date extension throws

What changed: 8.3 introduces DateError and DateException class families. An invalid modifier passed to DateTime::modify() now throws DateMalformedStringException, where 8.2 returned false.

What to look for: Booking, scheduling and reporting code that turns user text into dates. Wrap the call in try; the new classes extend Exception.

range() gets rules

What changed: Invalid boundaries now throw TypeError, and impossible steps throw ValueError. When one boundary is a digit in a string and the other a letter, range() now returns characters instead of numbers.

What to look for: Alphabet and number pickers that mix strings and integers as limits.

Static state in traits and arrays

What changed: A trait's static property is now redeclared in each class that uses it rather than shared through the parent. After an empty array receives a negative key, the next appended element takes the following number rather than 0.

What to look for: Shared registries implemented as static trait properties, and arrays seeded with negative indexes.

Two common calls deprecated

What changed: Calling get_class() or get_parent_class() bare, without an object, now logs a deprecation. So does using ++ on a string that is empty or not alphanumeric, or -- on one that is empty or not numeric.

What to look for: self::class or static::class replace the first; str_increment() and str_decrement() replace the second.

Assertion settings

What changed: assert_options(), the ASSERT_* constants and the assert.* ini settings are deprecated; zend.assertions is the setting to use.

What to look for: Test bootstraps and php.ini files that set assert.active or assert.exception to anything other than the default.

Other details

What changed: unserialize() notices become warnings, number_format() honours negative decimals, MT_RAND_PHP is deprecated, and PHP 8.3 on Windows needs at least Windows 8 or Windows Server 2012.

What to look for: Old Windows servers, and seeded random sequences that depended on the legacy Mersenne Twister variant.

Sources on php.net: 8.3 incompatible changes, 8.3 deprecations, 8.3 other changes.

Before the next step

Clear these on 8.3, and the move to 8.4 shrinks

8.3's support ends on 31 December 2027, so another PHP move is already on the calendar. Each of these 8.4 changes can be fixed in code that still runs on 8.3.

  • Nullable types spelled out. 8.4 deprecates a parameter that only accepts null because its default is null. Adding the ?, as in ?Logger $logger = null, is valid on 8.1 and 8.3 too.
  • Fatal user errors. trigger_error($message, E_USER_ERROR) is deprecated in 8.4; throw an exception instead.
  • CSV escape characters. Leaving out the $escape argument of fgetcsv(), fputcsv() and str_getcsv() is deprecated; pass it explicitly now.
  • mysqli housekeeping calls. mysqli_ping(), mysqli_kill() and mysqli_refresh() are deprecated in 8.4.
  • Extensions leaving PHP. From 8.4, IMAP, OCI8, PDO_OCI and PSpell are distributed through PECL only. If you use any of them, start on the replacement while 8.3 still bundles them.

php.net: migrating from PHP 8.3 to 8.4

Tooling

Analysers that help on a two-release move

For two releases, read php.net's two guides in full and use open source analysers to find every place a change applies. They inspect source code only, so behaviour changes such as the new range() results still need tests.

  • PHP_CodeSniffer and PHPCompatibility. With testVersion set to 8.1-8.3, the PHPCompatibility standard reports code that would fail on any of the three releases, useful while production stays on 8.1. Checks for recent PHP versions are only in its 10.0 pre-releases; the most recent, 10.0.0-alpha2, appeared in November 2025, and the README installs it with ^10.0.0@dev.
  • Rector. Its PHP sets are cumulative: withPhpSets(php83: true) includes every set up to 8.3. Starting with php82: true, reviewing the --dry-run output and then raising it keeps each change set small.
  • PHPStan. A phpVersion of 80300 makes it analyse the code as 8.3 code, regardless of the PHP version running PHPStan.
  • Composer. composer why-not php 8.3 names the packages holding the target back.
# report anything that fails on 8.1, 8.2 or 8.3
vendor/bin/phpcs -p . --standard=PHPCompatibility --runtime-set testVersion 8.1-8.3

# phpstan.neon
parameters:
    phpVersion: 80300

Tool documentation: PHPCompatibility, PHP_CodeSniffer, Rector, PHPStan.

Testing and rollout

Testing the move, and switching over

  1. Staging on 8.3.36 or later, with the production web server, PHP handler and a recent copy of the database.
  2. Extensions matched, comparing php -m with a phpinfo() page served by the web server, since the CLI may read another php.ini (php --ini shows which).
  3. Composer resolved against 8.3: config.platform.php set to 8.3.36, composer update, a committed lock file, then composer check-platform-reqs on staging to confirm the real runtime matches.
  4. Full logging: error_reporting = E_ALL, log_errors = On, display_errors = Off. The shipped php.ini-production drops deprecations, which is fine for live but hides the 8.2 and 8.3 notices you need to see here.
  5. Every route through the site tested: sign-in, search, forms, payments, emails, file uploads, cron and queue workers.
  6. A planned cutover, at a quiet time, with 8.1 left installed but unused and the error log under watch afterwards.

If you have to roll back

Rolling back means 8.1 again, so make sure the 8.1 you would return to is the patched build, not the one you ran before October. An unpatched 8.1 reopens CVE-2026-109618 and CVE-2026-109619, which have appeared in CISA's Known Exploited Vulnerabilities catalogue from 26 October 2026. Note the steps to switch back and any database changes to undo before you start, and use the fallback only for as long as it takes to fix what broke.

Platform constraints

Which platforms let 8.1 sites move straight to 8.3

On some platforms the PHP change can go first; on others the application must be upgraded before, or together with, PHP.

Run on both 8.1 and 8.3

  • Drupal 10.4 to 10.6 (8.1 to 8.4).
  • Joomla 5: needs 8.1, recommends 8.3.
  • Moodle 4.4 and 4.5: 8.1 minimum, 8.3 usable.
  • Laravel 10 (8.1 to 8.3), although its own security support ended on 4 February 2025.
  • OpenMage 20.14.0 and later (8.1 or newer).

Need an application upgrade for 8.3

  • Magento 2.4.6 lists 8.1 and 8.2 only; 2.4.7 lists 8.2 and 8.3.
  • PrestaShop 8.x stops at 8.1; 9.0 and 9.1 support 8.3.
  • Drupal 11.1 and later need 8.3, so plan any Drupal 11 move after the PHP change.
  • WooCommerce asks for 8.3 or greater; Nextcloud 35 supports 8.3 to 8.5.

Installing PHP 8.3: server update guides for Linux, cPanel, Plesk, shared hosting, Docker and Windows and IIS. Other guides: 8.1 to 8.4, 8.2 to 8.3, 8.2 to 8.4, 8.0 to 8.4, 7.4 to 8.3 and 7.4 to 8.4.

Quick answers

PHP 8.1 to 8.3 questions

Is there a PHP2Shell fix for PHP 8.1?

Not from the PHP project as a release. Its security team published a minimal source backport, the PHP-8.1-CVE-2026-109618-109619 branch, for people who compile PHP themselves. Some distributions ship their own patched 8.1 packages: Ubuntu's notice USN-8948-1, for instance, covers php8.1 on Ubuntu 22.04. Check with whoever supplies your PHP.

Backports explained

Why stop at PHP 8.3 rather than go to 8.4?

Go to 8.4 if your application supports it and you have time to test the extra release: it adds a year of security fixes. Stop at 8.3 when your platform tops out there, when you rely on IMAP, OCI8, PDO_OCI or PSpell and need time to replace them, or when the smaller change gets the 8.1 server retired sooner.

The PHP 8.1 to 8.4 guide

How long will PHP 8.3 be supported?

Until 31 December 2027. It left active support at the end of 2025 and now receives fixes for critical security issues only, released when needed.

PHP end of life dates

What does #[\AllowDynamicProperties] do, and should I rely on it?

It tells PHP 8.2 and later that a class, and every class extending it, may receive undeclared properties without a deprecation notice. It is a reasonable bridge for a large legacy class you cannot refactor yet. Declaring the properties remains php.net's preferred fix, and it makes the class clearer to read.

Do I have to upgrade my CMS or framework before moving to 8.3?

It depends on the release. Drupal 10.4 to 10.6 and Joomla 5 run on both 8.1 and 8.3, so PHP can move first. PrestaShop 8.x and Magento 2.4.6 cannot run on 8.3, so the platform upgrade has to come first or alongside.

PHP requirements by platform

3D rocket illustration above the call to find every exposed PHP server

Know your exposure

Find every PHP server you run

Patching the server you know about is the easy part. A vulnerability scan finds the forgotten staging site, the old microsite and the appliance with PHP inside, then tells you which ones are still exposed.