On 31 December 2026, security support for PHP 8.2 ends.
After that, every new vulnerability stays open.
On 1 January your application will still run, just as fast and just as stable as the day before. That is exactly why this date gets postponed so often. Nothing happens that anyone can see. Nothing more arrives, that is all.
This article covers whether the date affects you at all, which version you should move to and what the move from 8.2 costs. At the end there is a plan for the weeks left until December. What an end of life means in general is in the glossary. This is about this one date.
Does the date affect you at all?
The question sounds trivial. It is not. 31 December applies to PHP from php.net. Whether it applies to your PHP depends on where it comes from.
- Official Docker images and self-built binaries follow php.net. An image such as
php:8.2-fpmgets no new PHP releases after the date. - Debian 12 ships PHP 8.2 as a system package. The distribution has been in long-term support since June 2026, and the Debian LTS team patches its packages until 30 June 2028. Which packages are excluded is shown by
check-support-statusfrom thedebian-security-supportpackage. Here the date moves back by eighteen months. - Package repositories such as those of Ondřej Surý or Remi Collet follow php.net and drop a version from maintenance once it ends.
- With managed hosting, the provider decides. Some switch quietly, some charge extra for old versions, some withdraw them.
How far assumption and reality can drift apart is something I saw in a project in September. The local development environment ran on a base image from December 2024. CI tested every day against one from December 2025. The newer image had never been mirrored into the registry the developers pulled from. Nobody had decided that. It had simply happened.
So the first thing I check is every environment, not the repository:
php -v
php -m
dpkg -l 'php8.2*' 2>/dev/null | grep ^iiAnd I mean every one: production, staging, CI, workers, the server with the cron jobs. The one with the cron jobs is often the one still running something older, because nobody had it on the list during the last upgrade.
The problem arrives through Composer, not PHP
Suppose your distribution patches PHP 8.2 until 2028. Your dependencies still will not wait that long.
Symfony 8.0 requires PHP 8.4. Laravel 13 officially requires PHP 8.3, but from version 13.3 it allows Symfony 8 components and so in practice often ends up on 8.4. Generate the lock file on a machine with PHP 8.4, deploy it to 8.3, and the application greets you at startup with this message from Composer's platform check:
Composer detected issues in your platform: Your Composer dependencies require a PHP version ">= 8.4.0".That is the real mechanism behind an end of life. The interpreter does not become insecure. The security updates of your libraries appear in versions you can no longer install.
Composer itself tells you which packages hold you back:
composer why-not php 8.4And one setting is the first thing I put in place in every upgrade: the PHP version Composer resolves against lives in the project, not on whichever machine happens to run the update.
composer config platform.php 8.2During the move it says 8.2. On switchover day it becomes the target version. That way nobody can accidentally produce a lock file that only works on their own laptop.
8.3, 8.4 or 8.5?
| Version | Released | Security updates until | My assessment |
|---|---|---|---|
| PHP 8.3 | November 2023 | 31 Dec 2027 | buys a year, then the same question |
| PHP 8.4 | November 2024 | 31 Dec 2028 | my default |
| PHP 8.5 | November 2025 | 31 Dec 2029 | if Composer agrees |
| PHP 8.6 | 19 Nov 2026 | 31 Dec 2030 | not for a move in December |
PHP 8.3 buys you twelve months. In December 2027 you face the same question with the same effort. That is only worth it if 8.4 fails on a concrete blocker, such as an extension that no longer exists there. More on that shortly.
PHP 8.4 is my default. Two years of calm, a settled ecosystem, and Symfony 8 and current Laravel versions require it anyway.
So why not go straight to PHP 8.5? The objection is fair. One more year for almost the same effort. I decide it in one place: composer why-not php 8.5. If the list is empty, I take 8.5. If it contains packages you do not control yourself, I take 8.4 and plan 8.5 as routine maintenance for 2027.
PHP 8.6 comes out on 19 November. Putting a x.0 release into production five weeks before the deadline is not a plan. It is a bet.
What breaks between 8.2 and 8.4
The good news first: the hard part is behind you. If you run 8.2, you have already been through the PHP 8.0 break, meaning the TypeErrors, the changed comparison between strings and numbers and the removed legacy constructs.
If you are still facing it, the whole route is in PHP 7.4 to PHP 8.4.
Between 8.2 and 8.4 there is much less. Five places still hit old codebases:
- Implicitly nullable parameters. A signature like
function save(string $name = null)has been deprecated since 8.4 and must read?string $name = null. Mechanical, but spread across many files. Rector handles it completely. - Unbundled extensions. IMAP, OCI8, PDO_OCI and pspell are no longer part of the PHP core since 8.4, they now live in PECL. A mailbox fetch via
imap_open()or an Oracle connection is therefore not a note in the log but a blocker. It is the most common reason 8.4 does not work straight away. - The cost of bcrypt.
password_hash()withPASSWORD_DEFAULThas used cost factor 12 instead of 10 since 8.4. Existing hashes stay valid. But every login that rehashes viapassword_needs_rehash()then needs roughly four times the computing time. On a tightly sized server with a load peak at eight in the morning, you notice. - Smaller breaks from 8.3.
range()checks its arguments more strictly and throws errors on invalid values where it used to return a result quietly.get_class()without an argument is deprecated. Rare, but in old code not never. - Silenced notices from 8.2. Dynamic properties have been deprecated since 8.2. Many teams silenced the notices back then with
#[AllowDynamicProperties]or an adjustederror_reporting. That still holds under 8.4. But this is exactly where PHP 9 turns it into an error, and whoever does the upgrade should know how many places there are.
Two tools handle the mechanical part. PHPStan gets the target version, so it analyses against 8.4 and not against whatever version it happens to run on:
parameters:
phpVersion: 80400
level: 5Rector rewrites the signatures and the remaining mechanical changes:
<?php
use Rector\Config\RectorConfig;
return RectorConfig::configure()
->withPaths([__DIR__ . '/src'])
->withPhpSets(php84: true);I never commit a run over the whole project in one go. One rule, one commit, one review. A diff across 800 files is not bold, it is unreadable.
Which rules I switch off and which remainder stays manual is in Rector in a legacy project.
How PHPStan gets into a codebase that never had static analysis is in Introducing PHPStan into legacy code level by level.
Where the time really goes
The PHP move itself rarely blows the schedule. The dependencies it drags along do.
In the same project in September, after moving to Doctrine DBAL 4, the development environment failed to start with the message "Unknown database type enum". DBAL 4 no longer knows an ENUM type, and the database had such columns from migrations written in raw SQL. Every schema introspection failed.
The fix was one line:
$connection->getDatabasePlatform()->registerDoctrineTypeMapping('enum', 'string');The real work was deciding where that line belongs. Not in the application's connection configuration. There it would have switched every database connection from lazy to eager, because the platform is only known once the connection is established. It now sits in the two command line entry points, and only there.
A week later came the next round: doctrine/migrations 3.9, PHPStan 2.2, Codeception 5.3. While catching up, one line stood out that had been wrong for years:
$data = json_decode($json, true, JSON_THROW_ON_ERROR);The third parameter of json_decode() is the nesting depth, not the flags. The constant ended up in the call as a depth of just over four million. There was never an exception, and malformed JSON came back quietly as null. Correct is:
$data = json_decode($json, true, 512, JSON_THROW_ON_ERROR);No upgrade caused this bug. The upgrade found it.
That is the kind of finding that turns up in every upgrade and appears in no estimate. I plan a buffer for it. Not because I expect something to go wrong, but because old code contains assumptions that only surface once you touch it.
What the move from 8.2 costs
Less than most people expect. If the preconditions are right.
An application on 8.2 with Composer, a CI and a framework in a supported version is a job of a few days to about two weeks. Rector does most of it, PHPStan shows the rest, and the tests on the critical paths tell you whether it holds.
It gets more expensive in four places:
- The framework is lagging. Symfony 6.4 runs on PHP 8.4, but anyone heading for Symfony 8 has to get through the 7.4 deprecations first. With Laravel, versions 10 and 11 no longer get security updates, and the route to 13 goes through every major version in between. Then the PHP upgrade is the smaller part. Both cases have their own pages: Symfony upgrade and Laravel upgrade.
- An extension has been unbundled. Building a PECL extension once takes an hour. Carrying it into every image for years is a decision. It is usually cheaper to move the mailbox fetch to a library in pure PHP.
- There are no tests on the critical paths. Then every upgrade is flying blind, even a small one. Characterization tests come first, for checkout, login, billing and interfaces, and they cost more than the upgrade itself.
- PHP lives on a server someone set up by hand. Then the upgrade is a reinstallation with an uncertain outcome. In that case I rebuild the environment as an image, and the upgrade becomes a side effect.
How to get tests into code that never had any is in Testing legacy code when there are no tests.
And the alternative? Several vendors offer paid long-term support for PHP 8.2. That can be a bridge for a few months. But it does not solve the problem from the Composer section: your libraries keep moving anyway.
How I run a PHP upgrade, from the inventory to the switchover, is on a page of its own.
A plan for the weeks until December
31 December is not your deadline. Your deadline is earlier.
Many companies freeze changes to production systems from mid December. Anyone who has not switched by then either switches during the freeze or runs without security updates in January. From today, that leaves about eleven weeks.
- By mid October: inventory. Check every environment as above, run
composer why-not php 8.4, list the extensions and settle the target version. - By the end of October: CI against both versions. The pipeline runs against 8.2 and 8.4 in parallel. As long as 8.2 stays green, every change remains deployable.
- In November: rework in small steps. Rector rule by rule, PHPStan on the target version, dependencies caught up. Every step goes into the main branch on its own, no branch lives longer than a few days.
- End of November: staging on 8.4. Two weeks of real operation on staging, including cron jobs and workers, not just the web interface.
- Early December: switch over with a way back. The new environment stands in parallel, traffic is switched, and the old one stays up until the new one has proven itself. That is a blue/green deployment, and the way back is part of the delivery.
For the second step, a matrix in GitHub Actions is enough:
strategy:
matrix:
php: ['8.2', '8.4']
steps:
- uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}So that you do not hear about it eleven weeks before the date again in December 2028: An end-of-life calendar for the whole stack.
Frequently asked questions
Will my application still run on 1 January? Yes. Nothing switches off. There are simply no more security updates, and every vulnerability that becomes known afterwards stays open in your installation. For audits, cyber insurance and customers with security requirements, a version past its end of life is also a finding in patch management.
Can I skip 8.3? Yes. Going from 8.2 straight to 8.4 is the normal route. Whatever 8.3 changed, the analysis against 8.4 shows as well.
Is it enough if the hosting provider switches? Only if your code has been checked against the new version first. Otherwise the switch finds your bugs in production.
What I would do this week
If you are on PHP 8.2 today, I would run two commands this week: php -v on every environment and composer why-not php 8.4 in the project.
If both results are unremarkable, the upgrade is a small job. Then start in October, not in December. If the second list contains a framework or an extension, it is a project, and then every week counts.
PHP 8.2 will not get slower on 1 January. It will just never get more secure again.

