Linux Basics

Upgrading to PHP 8.5: breaking changes, deprecations and a safe server switch

A practical PHP 8.4 to 8.5 upgrade guide: the breaking changes that bite, the deprecations to clean up, how to find them, and how to switch PHP-FPM safely.

Published
Reading time
7 min read

Moving an application from PHP 8.4 to PHP 8.5 is usually a small step, but "usually" is not a plan. The official migration guide lists the backward-incompatible changes and deprecations; this guide picks out the ones that most often affect real applications and servers, shows how to find them in your code, and walks through switching a Debian or Ubuntu server without a long outage.

If you are coming from 8.3 or older, work through each intermediate migration guide (8.3 to 8.4, then 8.4 to 8.5) rather than jumping straight to the last one.

Breaking changes most likely to affect you

Configuration and engine

  • disable_classes is gone. The INI setting was removed because it broke engine assumptions. Configurations that still set it no longer get the protection they expected.
  • OPcache is part of the binary. It is always built in and loaded; zend_extension=opcache.so now emits a warning. Remove such lines from configuration you carry over from older versions. opcache.enable and opcache.enable_cli still work.
  • New warnings on casts. Casting a float (or a float-like string) that cannot be represented as an integer now warns, for both explicit and implicit int casts, and so does casting NAN to other types. Code that converts untrusted numbers may start logging warnings.
  • Destructuring non-arrays (other than null) with [] or list() now emits a warning.
  • Attributes on invalid targets: applying #[\Attribute] to an abstract class, enum, interface or trait is now a compile-time error instead of failing later in reflection.
  • class_alias() no longer accepts array or callable as alias names.

Extensions

  • PDO: constructor arguments used with PDO::FETCH_CLASS now follow normal call_user_func_array() semantics, so string keys act as named arguments. The numeric values of PDO::FETCH_GROUP, FETCH_UNIQUE, FETCH_CLASSTYPE, FETCH_PROPS_LATE and FETCH_SERIALIZE changed, which matters only if code stores or hard-codes the numbers instead of using the constants.
  • Sessions: writing session data where a $_SESSION key contains a pipe character (|) now emits a warning instead of failing silently, and session_start() validates its options array more strictly.
  • MySQLi: calling the mysqli constructor again on an already-constructed object now throws an Error.
  • Intl requires ICU 57.1 or later, and mbstring uses Unicode 17.0 data.
  • Standard: passing integer 0 as the locale argument of setlocale() now throws a TypeError; ArrayObject no longer accepts enums.
  • Many functions in Bzip2, POSIX, Sockets, SNMP, Tidy and others now throw ValueError for out-of-range or null-byte arguments that were previously tolerated.

Deprecations to clean up

Deprecations keep working in 8.5 but produce E_DEPRECATED messages. Fixing them now keeps logs readable and prepares for a future major version. The ones that appear most in older codebases:

  • Backtick shell execution (use shell_exec() explicitly, or better, avoid shelling out).
  • (boolean), (integer), (double), (binary) casts.
  • case labels ending with ; instead of :.
  • null as an array offset or as the key in array_key_exists() (use an empty string).
  • Incrementing non-numeric strings with ++ (use str_increment()).
  • __sleep()/__wakeup() (soft-deprecated; move to __serialize()/__unserialize()).
  • No-op cleanup calls: curl_close(), curl_share_close(), finfo_close(), imagedestroy(), xml_parser_free().
  • ReflectionProperty::setAccessible() and the other reflection setAccessible() methods, which no longer have any effect. These are common in older test suites and libraries.
  • Aliases mysqli_execute() and socket_set_timeout(); passing values outside 0 to 255 to chr(); passing a string that is not a single byte to ord().
  • The uri: PDO DSN scheme, deprecated over the risk of DSNs loaded from remote URIs.
  • Deriving $_SERVER['argc'] and $_SERVER['argv'] from the query string in non-CLI SAPIs: set register_argc_argv=0 and read $_GET or $_SERVER['QUERY_STRING'] instead.

How to find them in your code

Start with cheap searches, then let tools and tests do the rest. Run from the project root and exclude vendor/ for your own code:

grep -rnE '\((boolean|integer|double|binary)\)' --include='*.php' --exclude-dir=vendor .
grep -rnE '\b(curl_close|curl_share_close|finfo_close|imagedestroy|xml_parser_free|mysqli_execute|socket_set_timeout)\s*\(' --include='*.php' --exclude-dir=vendor .
grep -rn 'setAccessible(' --include='*.php' --exclude-dir=vendor .
grep -rnE 'disable_classes|opcache\.so' /etc/php/

Then:

  • Lint with the new binary. php8.5 -l compiles files without running them, and since PHP 8.3 it accepts several files at once. It catches syntax problems but not anything that only happens at runtime.
    find . -path ./vendor -prune -o -name '*.php' -print0 | xargs -0 -n 200 php8.5 -l | grep -v '^No syntax errors'
  • Run the test suite on 8.5 with deprecations visible. In the staging pool or php.ini: error_reporting = E_ALL, log_errors = On, display_errors = Off, then read the log.
  • Static analysis. Tools such as PHPStan can analyse code against a target version (its phpVersion parameter); make sure the tool version you install understands PHP 8.5.
  • Dependencies. composer prohibits php 8.5 (alias why-not) lists packages whose constraints block PHP 8.5. Update those first.

Switching a Debian or Ubuntu server

Install 8.5 next to the current version, test it, then move traffic. On Ubuntu 26.04 LTS, php8.5 is in the distribution archive. On Debian 13, which ships 8.4, PHP 8.5 currently requires a third-party repository such as packages.sury.org; read its official instructions and pin it before adding it.

sudo apt update
sudo apt install php8.5-fpm php8.5-cli php8.5-mysql php8.5-xml php8.5-mbstring php8.5-curl php8.5-intl
# compare loaded extensions with the old version
php8.4 -m > /tmp/mods-84.txt; php8.5 -m > /tmp/mods-85.txt; diff /tmp/mods-84.txt /tmp/mods-85.txt

Carry over only your own overrides (pool settings in /etc/php/8.5/fpm/pool.d/ and small files in conf.d/), not a whole old php.ini. Then point the web server at the new socket:

# Nginx: in the PHP location block
fastcgi_pass unix:/run/php/php8.5-fpm.sock;
sudo nginx -t && sudo systemctl reload nginx

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

Keep the 8.4 FPM service installed and running until you are confident. Rolling back is then just pointing the socket back and reloading. Remember that CLI cron jobs and queue workers use the php command, which Debian-style packages manage with update-alternatives; check php -v and set it explicitly with sudo update-alternatives --config php if needed.

Pitfalls

  • Upgrading PHP without upgrading the framework or CMS first. Check the vendor's supported versions.
  • Missing extensions after the switch, which typically shows up as class or function "not found" errors. The diff above catches them.
  • OPcache settings tuned for the old version and copied blindly. Start from the packaged defaults and re-apply only what you measured.
  • Forgetting worker processes (queues, schedulers, supervisors) that keep running the old binary until they are restarted.

What you should do

  • Read the official 8.4 to 8.5 migration guide once, end to end, and note the items that touch your code.
  • Run the searches, lint and test suite against 8.5 in staging, with all deprecations logged.
  • Clear blocking Composer constraints with composer prohibits php 8.5.
  • Install 8.5 side by side, switch the FPM socket, and keep the old version ready for a quick rollback.
  • After the switch, watch error logs for a few days, then remove the old PHP packages.

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.