Currently taking projects for 2027

Jonathan Bird Web Design & Development

Latest PHP Version: Current Release and Support Status

Last updated: October 24, 2026

If you only want the number: PHP 8.5.12 is the latest stable release of PHP, published on 21 October 2026. The next branch, PHP 8.6, is at Release Candidate 4 and is scheduled for general availability on 19 November 2026.

This month the patch number matters more than usual. PHP 8.5.12, 8.4.27, 8.3.36 and 8.2.35 are emergency security releases, and anything older on those branches is exposed to two critical remote code execution vulnerabilities. The vulnerability section covers what to check and what to do.

Outside of a week like this one, the version number on its own doesn't tell you much. For an application you have to keep running, what matters is how long your branch still gets security fixes and how much work it takes to move to one that does.

We maintain Laravel and Statamic applications for established organisations, so this comes round every year, usually somewhere between "the host just deprecated your PHP version" and "a dependency won't install any more". Below: current release numbers, every support date, the full PHP 8.6 schedule, how to check which PHP your server actually runs, and how to upgrade without taking production down.

The original runtime examples were recorded on 18 August 2026. Release numbers, support status and the security section were updated on 24 October 2026 using PHP's official announcements and security advisories.

The Latest PHP Version Right Now

"Latest" means three different things depending on who's asking, so here are all three.

What you mean by "latest" Version Released Safe for production?
Latest stable PHP release PHP 8.5.12 21 October 2026 Yes
Latest stable branch PHP 8.5 20 November 2025 Yes
Latest preview build PHP 8.6.0 RC4 21 October 2026 No, testing only

Supported branches do not necessarily release together, but this month they did. As of 24 October 2026, the current patch level for each is:

Branch Latest release Released Release type
PHP 8.5 8.5.12 21 October 2026 Critical security
PHP 8.4 8.4.27 21 October 2026 Critical security
PHP 8.3 8.3.36 21 October 2026 Critical security
PHP 8.2 8.2.35 21 October 2026 Critical security

Every supported branch got the same fix on the same day, which only happens when something serious lands. These are the minimum versions you should be running.

The previous round on 24 September (8.5.11, 8.4.26, 8.3.35 and 8.2.34) was a security release as well. On 8.5 and 8.4 it fixed eleven advisories, including TLS hostname verification falling back to the certificate's CN after a SAN mismatch, and an IPv6 bypass of PHP-FPM's listen.allowed_clients access list. If you skipped September, updating to the October release covers both. (There is no PHP 8.3.34. The branch went straight from 8.3.33 to 8.3.35.)

Check the PHP changelog and downloads page rather than calculating the next version from a four-week interval. Security releases don't wait for the schedule.

Release schedules are useful for planning maintenance, but actual announcements determine what is available. Distribution packages may also backport fixes without matching the upstream version number, as explained below.

Critical PHP Security Vulnerabilities (October 2026)

On 21 October 2026 the PHP project released PHP 8.5.12, 8.4.27, 8.3.36 and 8.2.35 to fix two critical vulnerabilities in PHP itself. Not in a framework, not in an extension you might not have installed: in the core request handling every PHP web server uses. PHP's own advisory is blunt: "All PHP users are strongly encouraged to update immediately."

CVE-2026-109618 CVE-2026-109619
PHP's name for it RCE via OOM corruption RCE via stale global state
Advisory GHSA-w8hw-w9fx-j79w GHSA-6wmf-4rv5-p3rp
Severity (PHP) Critical, CVSS v4.0 9.2 Critical, CVSS v4.0 9.2
Authentication needed None None
Fixed in 8.5.12, 8.4.27, 8.3.36, 8.2.35 8.5.12, 8.4.27, 8.3.36, 8.2.35

Researchers have started calling the pair "PHP2Shell", which is a fair summary of the worst case: a web request in, a shell on your server out.

What the Two Vulnerabilities Actually Are

CVE-2026-109618 (out-of-memory corruption). When PHP runs out of memory, it aborts the request on the spot. If that happens while PHP is still setting up internal request data, the cleanup code that runs afterwards can treat half-initialised memory as valid. An attacker who can push PHP over its memory limit at the right moment can turn that into code execution. The documented route is the request body itself: multipart requests without a usable Content-Length (which can slip past the initial post_max_size check), parts with unlimited headers, and deeply nested field names that make PHP do far more work than the request size suggests. The fix removes that abort-on-the-spot behaviour for out-of-memory errors entirely.

CVE-2026-109619 (stale global state). A PHP worker handles many requests in a row and keeps some global state between them. If a request is killed by a fatal error before that state is reset (out of memory and "Maximum execution time exceeded" both count), the state can point at memory that has already been freed. A later request landing on the same worker can then use it. That's a use-after-free, and PHP's advisory confirms a crafted heap layout can turn it into full code execution. The two confirmed routes are session upload progress, which is enabled by default and doesn't need your application to call session_start(), and the fatal error backtraces feature that arrived in PHP 8.5.

That last one is a bit awkward for this page, because fatal error backtraces are one of the 8.5 features we recommend further down. It's still a good feature. It was also the confirmed trigger.

In both cases the code runs as your web server's PHP user. On Apache with mod_php it can happen before any of your application code runs, which means your Laravel middleware, your authentication, and any request validation you've written never get a look in.

Which Setups Are at Risk

PHP's position is that every installation serving web requests is in scope. The advisories are more specific about how far each setup has actually been proven:

How PHP runs What the advisories say
Apache with mod_php Highest risk. Full remote code execution demonstrated against default memory and input limits, before any application code runs.
PHP-FPM (nginx, and most Laravel hosting) Exposed. CVE-2026-109619 was validated through session upload progress when the application performs a suitable operation. No complete exploit of CVE-2026-109618 has been shown on FPM, because FPM workers exit when request startup fails.
FrankenPHP (including Laravel Octane on FrankenPHP) In scope. Source analysis suggests it's likely susceptible to CVE-2026-109618, not yet demonstrated.
LiteSpeed (LSAPI) Likely susceptible, with application-specific exploits considered plausible.
php-cgi In scope.

Severity ratings differ depending on who you ask. PHP rates both at CVSS v4.0 9.2. NVD scores CVE-2026-109618 at 9.8 under CVSS 3.1. Red Hat rates it Important (8.1), on the basis that default RHEL deployments use PHP-FPM, where reliable exploitation needs precise heap manipulation. That's a useful reason to think FPM is harder to exploit than mod_php. It is not a reason to wait.

What Happened, and When

  • 20 October 2026: details of the vulnerabilities leaked before the coordinated disclosure. According to Huntress, a Hacker News post was taken down but screenshots kept circulating. The PHP security team published an advance notice the same day and pulled the release forward to 21 October at 14:00 UTC.
  • 21 October 2026: the four releases and PHP's security advisory went out at around 13:30 UTC, which was just before midnight in Brisbane. PHP 8.6.0 RC4 shipped the same day with the same fixes.
  • 21 October 2026: proof-of-concept exploits appeared publicly, and watchTowr reported exploitation in its honeypot network. Rapid7 found that each public exploit only worked against one specific combination of operating system, PHP version, and web server, so none was broadly reliable yet.
  • 23 October 2026: Unit 42 reported post-exploitation activity against a European government organisation, with the attackers reaching password hashes and MFA secrets within two hours of getting in. PHP also published an informational advisory for a third, much narrower issue (CVE-2026-115560, covered below).

The fact that today's public exploits are fiddly is the window to patch, not a reason to schedule it for next sprint. Exploits only get more portable from here.

How to Check Whether You're Patched

The same trap from the version-checking section below applies here, with higher stakes: the version that matters is the one serving web requests, not the one on your command line. Drop this in your web root, request it once through your site, then delete it:

1<?php
2 
3header('Content-Type: text/plain');
4 
5$fixedIn = [
6 '8.2' => '8.2.35',
7 '8.3' => '8.3.36',
8 '8.4' => '8.4.27',
9 '8.5' => '8.5.12',
10 '8.6' => '8.6.0RC4',
11];
12 
13$branch = PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION;
14 
15if (! isset($fixedIn[$branch])) {
16 printf("PHP %s (%s): no upstream release, ask your vendor or upgrade\n", PHP_VERSION, PHP_SAPI);
17} elseif (version_compare(PHP_VERSION, $fixedIn[$branch], '<')) {
18 printf("PHP %s (%s): VULNERABLE, update to %s\n", PHP_VERSION, PHP_SAPI, $fixedIn[$branch]);
19} else {
20 printf("PHP %s (%s): includes the fix\n", PHP_VERSION, PHP_SAPI);
21}

Here it is run against every PHP build on one of our machines on 24 October 2026:

1PHP 7.4.33 (cli): no upstream release, ask your vendor or upgrade
2PHP 8.2.32 (cli): VULNERABLE, update to 8.2.35
3PHP 8.3.32 (cli): VULNERABLE, update to 8.3.36
4PHP 8.4.23 (cli): VULNERABLE, update to 8.4.27
5PHP 8.5.8 (cli): VULNERABLE, update to 8.5.12
6PHP 8.6.0-dev (cli): VULNERABLE, update to 8.6.0RC4
7PHP 8.5.12 (cli-server): includes the fix

Six of seven builds needed updating, on a machine that gets updated regularly. The only patched one was the default php, because Homebrew had already pulled 8.5.12. The side-by-side [email protected] keg was still sitting on 8.5.8. That's exactly the "two PHPs on one box" problem, and it's why you check the one actually serving the site. The 8.6 line also shows why the script uses version_compare() rather than PHP_VERSION_ID: it correctly sorts an August 8.6.0-dev build below 8.6.0RC4.

One big caveat: this script is wrong on Ubuntu, Debian and RHEL packages. As explained below, those distributions backport fixes without changing the upstream version number. A fully patched Ubuntu 24.04 server still reports 8.3.6, and the script will call it vulnerable. On distro packages, check the full package version instead:

1dpkg -l 'php*' | grep '^ii'

The patched versions for the common long-term releases, as published on 21 October 2026:

OS PHP branch Patched package version (or newer)
Ubuntu 26.04 LTS 8.5 8.5.4-0ubuntu1.5
Ubuntu 24.04 LTS 8.3 8.3.6-0ubuntu0.24.04.13
Ubuntu 22.04 LTS 8.1 8.1.2-1ubuntu2.28
Debian 13 (trixie) 8.4 8.4.27-1~deb13u1
Debian 12 (bookworm) 8.2 8.2.35-1~deb12u1

Sources: Ubuntu's CVE tracker (USN-8948-1) and the Debian security tracker (DSA-6574-1 for trixie, DLA-4848-1 for bookworm). If you use Ondřej Surý's repository, it follows upstream numbering, so the script's answer is the right one.

Update, Then Restart Every Worker

Installing the new package doesn't fix running processes. A PHP-FPM worker that started yesterday is still running yesterday's PHP, and it will keep running it until it's recycled. PHP's advisory explicitly says to restart every worker:

  • PHP-FPM: restart every pool, for every installed version (systemctl restart php8.4-fpm, and so on). On a server with several PHP versions side by side, update and restart all of them, not just the one you think the site uses.
  • Apache with mod_php: restart Apache. mod_php lives inside the Apache process.
  • FrankenPHP and Laravel Octane: PHP is compiled into the FrankenPHP binary or image, so updating your operating system's PHP package changes nothing. You need a FrankenPHP build that bundles a patched PHP, then a restart.
  • Docker: pull, rebuild, and redeploy. If you pin the full PHP version (which we recommend below), bump the pin.
  • Managed hosting and shared hosting: check, don't assume. Run the script above through the site.

If You Can't Update Today

PHP is clear that there is no complete workaround and updating is the only safe choice. The configuration changes it lists reduce exposure to the specific attack paths found so far. They don't make a server safe, and they belong in the gap between reading this and patching, not instead of patching.

These have to go in php.ini or the FPM pool configuration so they apply before the request is parsed. ini_set() and .user.ini files are too late. For an FPM pool:

1; /etc/php/8.5/fpm/pool.d/www.conf, then restart the pool
2php_admin_flag[session.upload_progress.enabled] = off
3php_admin_flag[fatal_error_backtraces] = off
4php_admin_value[post_max_size] = 8M
5php_admin_value[upload_max_filesize] = 6M

What each line does:

  • session.upload_progress.enabled = off blocks the confirmed session upload progress path for CVE-2026-109619. Laravel and Statamic use their own session handling rather than PHP's native sessions, so they don't use this feature and switching it off costs nothing. PHP suggests leaving it off even after patching if you don't need it, and we agree.
  • fatal_error_backtraces = off (PHP 8.5 only) removes the confirmed fatal backtrace trigger. Remove the line on 8.4 and older, where the setting doesn't exist.
  • A low post_max_size narrows the out-of-memory route for CVE-2026-109618. PHP suggests 8M for applications without file uploads and 32M with them, with upload_max_filesize below it. Enforce limits at nginx or your proxy too (client_max_body_size in nginx), because PHP's own check can be bypassed by requests without a usable Content-Length.

PHP also lists setting memory_limit=-1 to remove the controllable out-of-memory failure. That removes PHP's memory ceiling entirely, so one bad request can eat the whole server. We'd only use it where a container or cgroup memory limit is already in place to kill the individual worker. Don't substitute RLIMIT_AS or RLIMIT_DATA, which PHP warns can trigger the vulnerable path themselves.

Hiding errors does nothing here. display_errors = off, error_reporting and the @ operator don't stop the vulnerable code from running.

The Follow-Up Advisory: CVE-2026-115560

On 23 October 2026 PHP published an informational advisory for a third issue, again involving session upload progress. It only applies to an unusual setup: an application configures session.serialize_handler as php_binary or php_serialize, then switches it to php with ini_set() before calling session_start(). PHP is fixing it in the next regular release, with a full advisory to follow.

The advice is the same setting as above: keep session.upload_progress.enabled = off until that release ships. If you already turned it off on 21 October, there's nothing more to do.

PHP 8.1 and Older

No upstream release fixes PHP 8.1 or anything older. That's what end of life means, and this is the week it stops being theoretical.

Because these vulnerabilities are so severe, the PHP security team did something it normally doesn't: it published minimal source patches for PHP 7.4, 8.0 and 8.1 for people who build PHP from source. Ubuntu patched the PHP 8.1 in 22.04, and older LTS releases through Ubuntu Pro. PHP 7.3 and older get nothing at all.

If you're relying on one of those, you got lucky this time. Don't plan on it twice. Use the patch to buy time for a real upgrade.

Signs You've Already Been Hit

If a server was exposed between 20 October and the moment you patched, it's worth a look. Based on what Huntress and watchTowr are telling people to hunt for:

  • PHP workers starting other programs. php-fpm, php-cgi, apache2 or httpd spawning sh, bash, python, perl, curl, wget or nc, or running reconnaissance commands like whoami, id and uname -a.
  • New files where there shouldn't be any. In a Laravel or Statamic app, public/ should contain exactly one PHP file, index.php. Anything else is worth explaining.
  • New cron entries, scheduled tasks, or user accounts you didn't create.
  • Odd entries in the logs: workers crashing or running out of memory, and unusually large or deeply nested multipart requests.

A quick first check from the application root:

1find public -name '*.php' -newermt '2026-10-20'

If you find something, isolate the server and keep the web server and PHP logs before you clean anything up. Rebuilding over the evidence makes it impossible to tell what was taken.

Supported PHP Versions and End-of-Life Dates

PHP gives every branch two years of active support (bug fixes and security fixes, with regular point releases), followed by two more years of critical security fixes only. Four years total, starting from a new branch that lands in late November most years.

Branch Initial release Active support until Security support until Status on 24 October 2026
PHP 8.5 20 Nov 2025 31 Dec 2027 31 Dec 2029 Current stable, actively supported
PHP 8.4 21 Nov 2024 31 Dec 2026 31 Dec 2028 Actively supported
PHP 8.3 23 Nov 2023 31 Dec 2025 31 Dec 2027 Security fixes only
PHP 8.2 8 Dec 2022 31 Dec 2024 31 Dec 2026 Security fixes only, EOL 31 Dec 2026
PHP 8.1 and older End of life, unsupported

The dates come from PHP's supported versions page, which is the only source worth trusting for this.

What "Security Fixes Only" Actually Means

This phrase gets read as "still fine" far more often than it should be. In practice:

  • Bugs are not fixed. If you hit a non-security bug in PHP 8.3, the answer is "upgrade to 8.5". There will not be a patch.
  • Releases are as-needed, not scheduled. A quiet month means nothing was urgent, not that the branch is healthy.
  • Only critical security issues qualify. Lower-severity security bugs can sit unfixed on a security-only branch.
  • Your dependencies leave first. Package maintainers drop old PHP versions on their own timetable, usually well before PHP's official EOL. You'll feel this as composer update quietly refusing to move packages long before php.net calls your branch dead.

If you're on PHP 8.2, mark the date: 31 December 2026. After that there are no more upstream patches at all, and any CVE published in January 2027 stays open on your server permanently unless your OS vendor backports it for you.

October's vulnerabilities show what that looks like in practice. PHP 8.2 got a fix because it still had ten weeks of support left. The equivalent bug in February 2027 would leave 8.2 servers exposed.

The PHP 8.6 Release Schedule

PHP 8.6 is in its release candidate phase, with RC4 published on 21 October 2026. The timetable, with what actually happened:

Date Milestone Status
2 Jul 2026 Alpha 1 Released
16 Jul 2026 Alpha 2 Released
30 Jul 2026 Alpha 3 Released
11 Aug 2026 All RFCs targeting 8.6 merged Done
13 Aug 2026 Beta 1 + soft feature freeze Released
27 Aug 2026 Beta 2 Released
10 Sep 2026 Beta 3 Released
22 Sep 2026 Hard feature freeze Done
24 Sep 2026 RC1 Skipped, a packaging mistake meant RC2 shipped instead
24 Sep 2026 RC2 Released
8 Oct 2026 RC3 Released
21 Oct 2026 RC4 Released, includes the October security fixes
5 Nov 2026 RC5 Scheduled
19 Nov 2026 General availability Scheduled

The release managers for 8.6 are Daniel Scherzer, Matteo Beccati, and Joe Ferguson.

If you saw the original schedule, the numbering shifted up by one after RC1 was skipped. The 5 November release is now RC5, and the 22 October slot (now RC4) shipped a day early alongside the security releases. The GA date hasn't changed.

The hard feature freeze has passed, so what's in RC4 is very close to what ships. This is the right time to get your test suite green against 8.6.

What is not safe is running it. Release candidates get no security support, and every 8.6 build before RC4 is vulnerable to the October RCE bugs. If you have an 8.6 beta or early RC sitting on a test server that's reachable from the internet, update it or switch it off.

Which PHP Version Should You Use?

Here's how to decide, by situation. Whichever branch you land on, run its latest patch release: as of October 2026 that means 8.5.12, 8.4.27, 8.3.36 or 8.2.35 at a minimum.

  • New project starting today: PHP 8.5. It's the current stable branch with active support until the end of 2027 and security fixes until the end of 2029. Check that your framework, your host, and any compiled extensions you depend on support it first, and verify the exact package versions in your lockfile.
  • Existing production app on PHP 8.4: no urgency, but put 8.5 on the roadmap. Active support for 8.4 ends 31 December 2026, and a change freeze over Christmas eats the last of that window.
  • On PHP 8.3: you're on security-only support already. Security fixes stop on 31 December 2027, so schedule the move to 8.5 well before then.
  • On PHP 8.2: this is current work, not future maintenance. The branch dies on 31 December 2026.
  • On PHP 8.1 or older: you are running unsupported software on a public server. Treat it as a security issue with a deadline that has already passed.
  • PHP 8.6: test environments only until 19 November 2026. Getting your test suite green against the release candidate now is cheap; being surprised in December is not.

There is one perfectly good reason to sit on PHP 8.4 rather than jumping to 8.5: a dependency that hasn't caught up yet.

Your Framework Sets the Real Floor and Ceiling

PHP's own support dates are only half the picture. Your framework has its own supported PHP range, and the intersection is what you can actually run.

For Laravel:

Laravel Supported PHP Released Bug fixes until Security fixes until
Laravel 13 8.3 to 8.5 17 Mar 2026 Q3 2027 17 Mar 2028
Laravel 12 8.2 to 8.5 24 Feb 2025 13 Aug 2026 24 Feb 2027
Laravel 11 8.2 to 8.4 12 Mar 2024 3 Sep 2025 12 Mar 2026

Worth noting for anyone on Laravel 12: bug-fix support ended on 13 August 2026. That branch is now on security-only support, with the same "no more bug fixes" reality PHP 8.3 users are living with. Laravel 13 requires PHP 8.3 as a minimum, so if you're on Laravel 12 and PHP 8.2, you have two upgrades to sequence, and PHP goes first.

Statamic follows Laravel: Statamic 6 requires Laravel 12.40 or 13, so it inherits whatever PHP range those support. If you're weighing up who does that work, there's a separate guide on hiring a Laravel developer, and a page covering what we do as Laravel developers in Brisbane.

How to Check Which PHP Version You're Actually Running

This sounds trivial and it isn't, because a typical server has more than one PHP binary and they routinely disagree. The command-line PHP that runs your cron jobs and your artisan commands is frequently not the PHP-FPM build that serves your website. That mismatch has cost us real hours.

Start With the CLI

1php -v

Which prints something like:

1PHP 8.5.8 (cli) (built: Jul 6 2026 06:38:54) (NTS clang 15.0.0)
2Copyright (c) The PHP Group

Note the (cli) in the first line. That's the SAPI, and it's telling you this number applies to the command line only.

Then Check the One That Serves Your Site

For PHP-FPM (the usual setup behind nginx):

1php-fpm -v
1PHP 8.5.8 (fpm-fcgi) (built: Jul 1 2026 03:46:27) (NTS)

The (fpm-fcgi) confirms you're looking at the right binary. On a server with several versions installed, be explicit: /usr/sbin/php8.4-fpm -v, or on macOS with Homebrew, /opt/homebrew/opt/[email protected]/sbin/php-fpm -v.

The most reliable answer, though, is to ask the running application rather than a binary on disk. Drop this in your web root temporarily:

1<?php
2 
3header('Content-Type: text/plain');
4 
5printf("Version: %s\n", PHP_VERSION);
6printf("SAPI: %s\n", PHP_SAPI);
7printf("Binary: %s\n", PHP_BINARY);
8printf("php.ini: %s\n", php_ini_loaded_file() ?: '(none)');
9printf("Zend: %s\n", zend_version());

Here's that same file, run two ways on the same machine. First served over HTTP, by a PHP build that isn't the one on the PATH:

1Version: 8.6.0-dev
2SAPI: cli-server
3Binary: /opt/homebrew/Cellar/[email protected]/8.6.0_1/bin/php
4php.ini: /opt/homebrew/etc/php/8.6/php.ini
5Zend: 4.6.0-dev

And from the command line:

1Version: 8.5.8
2SAPI: cli
3Binary: /opt/homebrew/Cellar/php/8.5.8/bin/php
4php.ini: /opt/homebrew/etc/php/8.5/php.ini
5Zend: 4.5.8

Same script, two different PHP versions, two different php.ini files. That's the trap, and it's why "I checked, we're on 8.5" is not an answer until you say which 8.5 you checked. (That demonstration used PHP's built-in server, so the SAPI reads cli-server. On a real nginx box it will say fpm-fcgi, and under Apache's module it will say apache2handler. The point is the same: whatever value comes back is the one that matters, because that's the PHP actually running your site.)

Delete that file when you're done. Same goes for phpinfo(), which is the classic version of this and leaks considerably more: absolute paths, loaded extensions, environment variables, and your full ini configuration. It's a reconnaissance gift to anyone scanning your site. If you need phpinfo(), put it behind auth or run it from the CLI with php -i.

From Inside Laravel

1php artisan about
1Environment ................................................................
2Application Name ............................................. Jonathan Bird
3Laravel Version .................................................... 12.66.0
4PHP Version .......................................................... 8.5.8
5Composer Version .................................................... 2.10.1
6Environment .......................................................... local

One command for the PHP version, the Laravel version, the Composer version, and your cache states. It's the first thing we run on any app we've inherited. Just remember it reports the CLI PHP, because that's what's executing it. Run it over SSH and you're seeing your cron environment, not your web environment.

Ask Composer Whether Your Code Even Fits

Version numbers matter less than whether your dependencies agree with them. Composer will tell you directly:

1composer check-platform-reqs
1Checking platform requirements for packages in the vendor dir
2composer-runtime-api 2.2.2 success
3ext-curl 8.5.8 success
4ext-dom 20031129 success
5ext-mbstring * success provided by symfony/polyfill-mbstring
6ext-openssl 8.5.8 success

That validates the current machine. The far more interesting question is whether a future PHP version would work, and there's a command for that too:

1composer why-not php 8.6

Run against this very site, it returns:

1nette/schema v1.3.5 requires php (8.1 - 8.5)
2nette/utils v4.1.5 requires php (8.2 - 8.5)

Two transitive dependencies with an upper bound of PHP 8.5. Nothing in the application code blocks PHP 8.6; two packages nobody chose directly do. This is the fastest way to find out whether a PHP upgrade is a config change or a dependency project, so run it before you plan the work.

Version Gates in Code (and One Real Trap)

Inside PHP, you have two ways to branch on version:

1if (PHP_VERSION_ID >= 80500) {
2 // PHP 8.5 or newer
3}
4 
5if (version_compare(PHP_VERSION, '8.5.0', '>=')) {
6 // PHP 8.5 or newer
7}

They are not equivalent, and the difference bites when you're testing against a beta. PHP_VERSION_ID is computed as major * 10000 + minor * 100 + release, so it ignores any pre-release suffix entirely.

Running on a PHP 8.6 beta build:

1Running: 8.6.0-dev (PHP_VERSION_ID 80600)
2PHP_VERSION_ID >= 80600: true
3version_compare >= 8.6.0: false

Both are "correct" and they disagree. PHP_VERSION_ID says you're on 8.6.0 because the numbers match. version_compare() applies semver-style pre-release ordering, where 8.6.0-dev, 8.6.0beta1, and 8.6.0RC1 all sort below the final 8.6.0.

Which one you want depends on the question. For "does this build have the 8.6 feature I need?", PHP_VERSION_ID is right, because betas do have the features. For "is this a supported production release?", version_compare() is right, because betas are not. Picking the wrong one is how a compatibility shim silently disables itself on a test server.

Why Your Server Reports an Older Version Than You Expect

You check php -v on your Ubuntu box, it says 8.3.6, and the current 8.3 release is 8.3.33. Twenty-seven releases behind. Time to panic?

Usually not. Enterprise Linux distributions don't rebase PHP to new upstream point releases during a distribution's life. They freeze an upstream version and backport security fixes into it, keeping the upstream version number and incrementing their own package revision instead.

Real examples, from August 2026 and after the October security fixes:

OS PHP branch Package in August 2026 Patched package, 21 October 2026
Ubuntu 24.04 LTS 8.3 8.3.6-0ubuntu0.24.04.10 8.3.6-0ubuntu0.24.04.13
Ubuntu 22.04 LTS 8.1 8.1.2-1ubuntu2.25 8.1.2-1ubuntu2.28
Debian 13 (trixie) 8.4 8.4.23-1~deb13u1 8.4.27-1~deb13u1
Debian 12 (bookworm) 8.2 8.2.32-1~deb12u1 8.2.35-1~deb12u1

Ubuntu 24.04 says 8.3.6, which upstream released in April 2024. The .13 on the end is Ubuntu's thirteenth revision of that package, carrying backported fixes for everything patched since, including both October RCE fixes. php -v on that server reports 8.3.6 before and after the critical patch. So the version string is misleading but the machine may well be current. Debian's packages for these branches, by contrast, follow upstream point releases, so their numbers line up with php.net.

Two important caveats:

  1. This only holds while the distro still supports that PHP branch. Ubuntu 22.04's PHP 8.1 went upstream-EOL on 31 December 2025. Canonical still patches it for the life of 22.04, but you're now depending on one vendor's backporting effort rather than the PHP project's. That's a meaningfully thinner safety net. In October 2026 it held: Canonical shipped the PHP 8.1 fix on release day. That's a good outcome, not a guarantee.
  2. You cannot tell from the version number alone. 8.1.2 with no revision suffix and 8.1.2-1ubuntu2.25 look nearly identical and are years apart in patch coverage. Check the full package version (dpkg -l | grep php or rpm -q php), not php -v.

If you want to actually track upstream releases on Debian or Ubuntu, the standard answer is Ondřej Surý's PPA / repository, which packages every current PHP branch and follows upstream point releases. On RHEL and derivatives, Remi's repository plays the same role. On macOS, Homebrew's php formula tracks the current stable branch, with [email protected] through [email protected] installable alongside it, and the shivammathur/php tap adds older and pre-release builds (which is how the PHP 8.6 examples in this article were tested).

For containers, the official Docker images track upstream precisely, and publish preview tags alongside stable ones: php:8.5-fpm, php:8.4-cli, and php:8.6.0beta1-cli were all available in August 2026. Pin the full version in production. php:8.5-fpm will silently roll you onto a new point release on rebuild, which is usually what you want for security and occasionally what breaks your build at 4pm on a Friday.

What You Actually Gain by Upgrading

Support dates are the reason you must upgrade. Here's the reason you'd want to. Everything below was run on the version stated.

PHP 8.4: Property Hooks and Asymmetric Visibility

The two headline features both reduce boilerplate that PHP developers have been writing by hand for twenty years.

1class Invoice
2{
3 public private(set) string $reference;
4 
5 public string $customer {
6 get => $this->customer;
7 set (string $value) => trim($value);
8 }
9 
10 public function __construct(string $reference, string $customer)
11 {
12 $this->reference = $reference;
13 $this->customer = $customer;
14 }
15}
16 
17$invoice = new Invoice('INV-1042', ' Impact Office Supplies ');
18 
19var_dump($invoice->customer);
20// string(22) "Impact Office Supplies"
21 
22$invoice->reference = 'INV-9999';
23// Error: Cannot modify private(set) property Invoice::$reference from global scope

private(set) gives you a property that's publicly readable but only writable from inside the class, with no getter method required. Property hooks let a plain property normalise its own input. Between them, a large amount of accessor boilerplate simply stops being necessary.

PHP 8.4 also added array functions people have been pulling in from packages for years, and dropped the parentheses requirement on immediate method calls:

1$branches = ['8.2', '8.3', '8.4', '8.5'];
2 
3var_dump(array_find($branches, fn (string $b): bool => $b > '8.3')); // string(3) "8.4"
4var_dump(array_any($branches, fn (string $b): bool => $b === '8.5')); // bool(true)
5var_dump(array_all($branches, fn (string $b): bool => str_starts_with($b, '8.'))); // bool(true)
6 
7var_dump(new Invoice('INV-1043', 'ROH Wheels')->customer); // no wrapping parens needed

On PHP 8.3 that first class definition doesn't even parse: Fatal error: Multiple access type modifiers are not allowed.

PHP 8.5: The Pipe Operator, Clone With, and a Real URI Parser

The pipe operator is the one you'll notice daily. It turns inside-out nested calls into something you read left to right:

1$title = ' Latest PHP Version ';
2 
3$slug = $title
4 |> trim(...)
5 |> strtolower(...)
6 |> (fn (string $value): string => str_replace(' ', '-', $value));
7 
8var_dump($slug);
9// string(18) "latest-php-version"

clone with finally makes the "wither" pattern on readonly classes bearable. Before 8.5, changing one property on a readonly object meant unpacking the whole thing through get_object_vars() and rebuilding it. Now it's one call:

1final readonly class Money
2{
3 public function __construct(
4 public int $amount,
5 public string $currency,
6 ) {}
7 
8 public function withAmount(int $amount): self
9 {
10 return clone($this, ['amount' => $amount]);
11 }
12}
13 
14var_dump((new Money(2500, 'AUD'))->withAmount(3000));
15// object(Money)#2 (2) { ["amount"]=> int(3000) ["currency"]=> string(3) "AUD" }

And PHP finally has a standards-compliant URI parser built in, rather than parse_url(), which has never fully implemented any specification:

1$uri = new Uri\Rfc3986\Uri('https://www.php.net/supported-versions.php');
2 
3var_dump($uri->getHost(), $uri->getPath());
4// string(11) "www.php.net"
5// string(23) "/supported-versions.php"

Also in 8.5: array_first() and array_last(), the #[\NoDiscard] attribute for functions whose return value must not be ignored, static closures allowed in constant expressions (so you can put one in an attribute), persistent cURL share handles, and backtraces on fatal errors, which is a small change that pays for itself the first time you're staring at a Laravel 500 error caused by an execution timeout. (Fatal error backtraces were also the confirmed trigger for CVE-2026-109619 before 8.5.12. On a patched release they're safe to keep on, which is the default.)

PHP 8.6: What's Already Landed

The examples below are from the original August review of a PHP 8.6 preview build. The PHP 8.6 timetable distinguishes soft and hard freezes; check the current preview release notes before relying on a feature or syntax.

Partial function application is the big one. You can now bind some arguments to a function and leave the rest as placeholders, producing a new callable:

1$toSlug = str_replace(' ', '-', ?);
2 
3var_dump(array_map($toSlug, ['latest php version', 'supported php versions']));
4// array(2) {
5// [0]=> string(18) "latest-php-version"
6// [1]=> string(22) "supported-php-versions"
7// }

That single ? replaces an entire category of throwaway arrow functions, and it pairs neatly with 8.5's pipe operator.

Readonly properties can declare defaults, which removes an awkward gap where a readonly property could only ever be set in a constructor:

1class Release
2{
3 public readonly string $channel = 'stable';
4 
5 public function __construct(public readonly string $version) {}
6}
7 
8$release = new Release('8.6.0');
9var_dump($release->channel); // string(6) "stable"
10 
11$release->channel = 'beta';
12// Error: Cannot modify readonly property Release::$channel

Enums support __debugInfo(), so an enum can control how it appears in var_dump() output:

1enum Branch: string
2{
3 case Php84 = '8.4';
4 case Php85 = '8.5';
5 
6 public function __debugInfo(): array
7 {
8 return ['branch' => $this->value, 'activeSupport' => $this === self::Php85];
9 }
10}
11 
12var_dump(Branch::Php85);
13// enum(Branch::Php85) (2) {
14// ["branch"]=> string(3) "8.5"
15// ["activeSupport"]=> bool(true)
16// }

#[\Override] now applies to class constants, including enum cases, so the compiler catches a constant you meant to override but misspelled:

1interface HasBranch
2{
3 const string CURRENT = '8.5';
4}
5 
6final class Current implements HasBranch
7{
8 #[\Override]
9 public const string CURRENT = '8.6';
10}

There's also a new Time\Duration class, a stream errors API, TLS session resumption and 0-RTT early data for stream contexts, a polling API, and a Uri\Rfc3986\UriBuilder to complement 8.5's URI extension.

Alongside that, 8.6 brings a substantial deprecation round: is_long(), is_integer(), is_double(), doubleval(), spl_object_hash(), spl_classes(), metaphone(), strcoll(), mysqli_get_charset(), and mbregex (because the underlying Oniguruma library is no longer maintained). None of them break anything in 8.6, but a deprecation is how PHP signals what's on track for removal in the next major version, so this is your advance notice.

How to Upgrade Without Taking Production Down

This is the part that decides whether the upgrade is a half-day or a fortnight.

1. Find Out What Blocks You Before Planning Anything

1composer why-not php 8.5

If this returns packages, that's your actual scope of work: either those packages have newer versions that support your target, or they're abandoned and need replacing. Find out now. This is the step that turns "quick PHP upgrade" into "and we also have to replace our PDF library", and you want that discovered on day one.

2. Lint the Whole Codebase Against the Target Binary

Before running a single test, parse every file with the PHP version you're moving to. Syntax and compile-time errors surface instantly and cost nothing to find:

1find app config routes database tests -name '*.php' -print0 \
2 | xargs -0 -P 4 -n 1 /usr/bin/php8.5 -l \
3 | grep -v '^No syntax errors'

Point -l at the target binary, not your current one. Anything printed is a file that won't even compile.

3. Turn Deprecations Into Something You Can See

This is where most upgrade pain actually lives, because deprecations are silent until they aren't. Here's a small file that's completely valid PHP:

1<?php
2 
3function reportTotal(int $cents = null): string
4{
5 $dollars = (integer) round($cents / 100);
6 
7 return "Total: {$dollars}";
8}
9 
10echo reportTotal(2500), "\n";
11 
12$cache = [];
13$cache[null] = 'value';

Run it on PHP 8.3 with error_reporting(E_ALL) and it's completely clean. Not one warning.

On PHP 8.4:

1Deprecated: reportTotal(): Implicitly marking parameter $cents as nullable is
2deprecated, the explicit nullable type must be used instead

On PHP 8.5:

1Deprecated: Non-canonical cast (integer) is deprecated, use the (int) cast instead
2Deprecated: reportTotal(): Implicitly marking parameter $cents as nullable is deprecated...
3Deprecated: Using null as an array offset is deprecated, use an empty string instead

Three real problems in a dozen lines, invisible on the version you're running. Scale that up to a codebase and you understand why upgrades stall.

So make deprecations loud in CI and staging. Set error_reporting = E_ALL and log rather than display them, then run your full test suite on the target version and read the log. Laravel has a dedicated channel for exactly this in config/logging.php, and it's switched off by default:

1'deprecations' => [
2 'channel' => env('LOG_DEPRECATIONS_CHANNEL', 'null'),
3 'trace' => env('LOG_DEPRECATIONS_TRACE', false),
4],

That 'null' default is why you've probably never seen a deprecation in laravel.log. On staging, point it somewhere real and turn tracing on:

1LOG_DEPRECATIONS_CHANNEL=stack
2LOG_DEPRECATIONS_TRACE=true

The stack trace is the part that matters, because it tells you whether the deprecation is in your code or inside a vendor package. That distinction changes the fix entirely: one is a five-minute edit, the other is a dependency upgrade.

For a static sweep without running anything, Rector configured with ->withPhpSets() will both find and automatically fix a large share of version-specific issues, and PHPStan will catch removed functions and changed signatures.

4. Check Your Extensions Separately

Composer's platform checks cover extensions declared in composer.json, but PECL extensions that only exist in your ini configuration (Redis, imagick, xdebug, ioncube, New Relic, and anything commercial) are their own migration. Compiled extensions must be rebuilt for each PHP minor version. Compare the two environments directly:

1diff <(php8.3 -m) <(php8.5 -m)

Commercial and proprietary extensions are the ones that most often block an upgrade outright, because their vendor sets the timetable, not you.

5. Roll It Out in the Right Order

Sequence matters more than speed:

  1. Local first, then CI. Get the full test suite green on the target version before touching a server.
  2. Staging on identical infrastructure. Same OS, same extensions, same FPM configuration, same opcache settings. A staging box built differently from production will happily hide the problem you're trying to find.
  3. CLI and web together. Update the FPM pool and the CLI binary, or you'll get cron jobs and queue workers running a different PHP version from your web requests. That produces bugs that only appear in queued jobs and are miserable to trace.
  4. Have the rollback ready before you start. With multiple PHP versions installed side by side, rolling back is a config change and an FPM restart. Know the exact command before you need it.
  5. Watch the logs for a full business cycle. Deprecations and edge-case failures show up on the paths your test suite doesn't cover: the monthly invoice run, the nightly ERP sync, the one report a single user opens on Fridays.

If nobody on your team owns this, it belongs in a website maintenance plan.

Common Mistakes

A few that come up repeatedly:

  • Assuming one PHP version per server. Check CLI, FPM, and any Apache module separately. They disagree more often than they agree.
  • Updating the package and not restarting the workers. Running PHP-FPM workers, Apache with mod_php, and Octane processes keep the old PHP in memory until they restart. A security patch isn't applied until the process that serves requests has been replaced.
  • Reading the distro patch number as the upstream one. 8.3.6-0ubuntu0.24.04.10 is not two years of missing security patches, whatever php -v implies.
  • Upgrading PHP and the framework in the same deploy. When it breaks, you won't know which one did it. Move PHP first, verify, then move the framework.
  • Trusting composer.json's "php": "^8.2" as evidence. That's the floor your app claims to support, not what the server runs and not what your dependencies allow. composer why-not is the real check.
  • Leaving phpinfo.php in the web root. It's still one of the most common findings in a basic security scan.
  • Treating EOL as a hard cliff. The practical deadline is earlier: it's the day a package you need stops supporting your version, and that's set by maintainers, not php.net.

Frequently Asked Questions

What is the latest version of PHP?

PHP 8.5.12, released on 21 October 2026, is the latest stable release. PHP 8.5 is the current stable branch, first released on 20 November 2025.

Is my PHP version affected by CVE-2026-109618 and CVE-2026-109619?

Yes, unless you're on PHP 8.5.12, 8.4.27, 8.3.36, 8.2.35 or newer, or on a distribution package that has backported the fix. Every PHP server handling web requests is in scope, with Apache mod_php at highest risk. Update, then restart every PHP-FPM pool, Apache, and any Octane or FrankenPHP workers. PHP 8.1 and older have no upstream release; use your OS vendor's patched package or upgrade.

Is there a workaround for the October 2026 PHP vulnerabilities?

Not a complete one. Turning off session.upload_progress.enabled, turning off fatal_error_backtraces on PHP 8.5, and lowering post_max_size all reduce exposure to the known attack paths, but PHP's position is that updating is the only safe fix.

Is PHP 8.6 out yet?

Not as a stable release. PHP 8.6 is at RC4, published on 21 October 2026, with RC5 due on 5 November and general availability scheduled for 19 November 2026. Use it in test environments only until the stable release is announced, and make sure any 8.6 build you're testing is RC4 or later.

Which PHP version should I use in production?

PHP 8.5 for anything new, assuming your dependencies allow it, on 8.5.12 or later. PHP 8.4 is a reasonable conservative target but its active support ends 31 December 2026. Anything on 8.3 or below is already security-fixes-only.

When does PHP 8.2 reach end of life?

31 December 2026. After that it receives no further patches of any kind from the PHP project. PHP 8.2.35, released on 21 October 2026 with the critical RCE fixes, may well be one of the last 8.2 releases.

When does PHP 8.4 stop being supported?

Active support ends 31 December 2026 and security support ends 31 December 2028.

How do I check my PHP version?

php -v for the command line, php-fpm -v for the version serving your website, and php artisan about inside a Laravel app. Because those can differ, the definitive answer comes from a script that prints PHP_VERSION and PHP_SAPI and is served through the same path as your application. Delete it afterwards.

Is it safe to run a PHP version that's on security-only support?

For a limited period, with a plan. What you give up is bug fixes, non-critical security fixes, and, in practice, the willingness of package maintainers to support you. It's a holding position, not a destination.

Does upgrading PHP break my site?

It can, and the risk scales with how many versions you skip. Going 8.4 to 8.5 is usually uneventful. Going 8.0 to 8.5 crosses several rounds of deprecations and removals. The upgrade path above (dependency check, lint sweep, deprecation logging, staged rollout) is what keeps it predictable.

Which PHP version does Laravel 13 need?

PHP 8.3 as a minimum, supported through 8.5. Laravel 12 supports 8.2 through 8.5.

Verify Any of This Yourself

Version numbers go stale, including the ones on this page. These are the sources we check, and the ones you should trust over any blog post, this one included:

We keep this page current as releases land. Release numbers, support status and the security section were rechecked on 24 October 2026. The patch-check script was run on 24 October; the other runtime output examples remain from the original 18 August review, and are not a claim that every example was rerun on the latest patches.

If you're staring at a PHP upgrade you'd rather not do yourself, or you've inherited an application on a version that's already past its end-of-life date, that's a fair bit of what we do. Get in touch and we'll tell you honestly how big a job it is.

Syntax highlighting by Torchlight

Bring us the website or platform decision you need to work through

Book a free strategy session with the senior people who scope and deliver the work. We will help clarify the problem, test assumptions and identify a practical next step—without turning the session into a generic sales call.