Latest PHP Version: Current Release and Support Status
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 upgrade2PHP 8.2.32 (cli): VULNERABLE, update to 8.2.353PHP 8.3.32 (cli): VULNERABLE, update to 8.3.364PHP 8.4.23 (cli): VULNERABLE, update to 8.4.275PHP 8.5.8 (cli): VULNERABLE, update to 8.5.126PHP 8.6.0-dev (cli): VULNERABLE, update to 8.6.0RC47PHP 8.5.12 (cli-server): includes the fixSix 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 pool2php_admin_flag[session.upload_progress.enabled] = off3php_admin_flag[fatal_error_backtraces] = off4php_admin_value[post_max_size] = 8M5php_admin_value[upload_max_filesize] = 6MWhat each line does:
session.upload_progress.enabled = offblocks 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_sizenarrows the out-of-memory route for CVE-2026-109618. PHP suggests 8M for applications without file uploads and 32M with them, withupload_max_filesizebelow it. Enforce limits at nginx or your proxy too (client_max_body_sizein nginx), because PHP's own check can be bypassed by requests without a usableContent-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,apache2orhttpdspawningsh,bash,python,perl,curl,wgetornc, or running reconnaissance commands likewhoami,idanduname -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 updatequietly 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 -vWhich prints something like:
1PHP 8.5.8 (cli) (built: Jul 6 2026 06:38:54) (NTS clang 15.0.0)2Copyright (c) The PHP GroupNote 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 -v1PHP 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<?php2 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-dev2SAPI: cli-server3Binary: /opt/homebrew/Cellar/[email protected]/8.6.0_1/bin/php4php.ini: /opt/homebrew/etc/php/8.6/php.ini5Zend: 4.6.0-devAnd from the command line:
1Version: 8.5.82SAPI: cli3Binary: /opt/homebrew/Cellar/php/8.5.8/bin/php4php.ini: /opt/homebrew/etc/php/8.5/php.ini5Zend: 4.5.8Same 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 about1Environment ................................................................2Application Name ............................................. Jonathan Bird3Laravel Version .................................................... 12.66.04PHP Version .......................................................... 8.5.85Composer Version .................................................... 2.10.16Environment .......................................................... localOne 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-reqs1Checking platform requirements for packages in the vendor dir2composer-runtime-api 2.2.2 success3ext-curl 8.5.8 success4ext-dom 20031129 success5ext-mbstring * success provided by symfony/polyfill-mbstring6ext-openssl 8.5.8 successThat 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.6Run 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 newer3}4 5if (version_compare(PHP_VERSION, '8.5.0', '>=')) {6 // PHP 8.5 or newer7}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: true3version_compare >= 8.6.0: falseBoth 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:
- 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.
- You cannot tell from the version number alone.
8.1.2with no revision suffix and8.1.2-1ubuntu2.25look nearly identical and are years apart in patch coverage. Check the full package version (dpkg -l | grep phporrpm -q php), notphp -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 scopeprivate(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 neededOn 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 = $title4 |> 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::$channelEnums 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.5If 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 is2deprecated, the explicit nullable type must be used insteadOn PHP 8.5:
1Deprecated: Non-canonical cast (integer) is deprecated, use the (int) cast instead2Deprecated: reportTotal(): Implicitly marking parameter $cents as nullable is deprecated...3Deprecated: Using null as an array offset is deprecated, use an empty string insteadThree 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=stack2LOG_DEPRECATIONS_TRACE=trueThe 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:
- Local first, then CI. Get the full test suite green on the target version before touching a server.
- 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.
- 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.
- 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.
- 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.10is not two years of missing security patches, whateverphp -vimplies. - 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-notis the real check. - Leaving
phpinfo.phpin 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:
- php.net/releases for every stable point release and its date
- php.net/supported-versions.php for lifecycle and end-of-life dates
- php.net/archive/2026.php for security advisories and alpha, beta, and RC announcements
- PHP's GitHub security advisories for the technical detail behind each CVE
- wiki.php.net/todo/php86 for the PHP 8.6 release timetable
- laravel.com/docs/13.x/releases for Laravel's supported PHP ranges
- Your OS vendor's security tracker (Ubuntu, Debian, Red Hat) for backported package versions
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