On 20 October, word leaked that two critical bugs in PHP itself were about to go public. A day later they had a name, PHP2Shell, and attackers already had a head start. I run a handful of Laravel applications for clients, and before the patch was out, every one of them was offline.
This is how I decided to pull the plug, why my hosting setup turned it into a calm afternoon instead of a crisis, and what I would check on your own Laravel servers now that the fix is out.
The short version
PHP2Shell (CVE-2026-109618 and CVE-2026-109619) lets an unauthenticated attacker run code on almost any PHP server. Update to PHP 8.5.12, 8.4.27, 8.3.36 or 8.2.35, restart every PHP process including your queue workers, and rotate your secrets if you were exposed. Small, isolated, up-to-date servers made it easy for me to go offline and come back one server at a time.
What is PHP2Shell?
PHP2Shell is the nickname for two critical vulnerabilities in the PHP interpreter, both rated 9.2 under CVSS v4.0:
- CVE-2026-109618: PHP can run out of memory while it sets up a request, and the cleanup afterwards works on data that was never fully initialised. That can be turned into remote code execution.
- CVE-2026-109619: state from one request survives after its memory has been freed, so a later request on the same worker can use it. A classic use-after-free.
Because the bugs live in PHP and not in a framework, it does not matter how well your Laravel code is written. PHP says every installation that serves web requests is at risk: PHP-FPM, Apache with mod_php, php-cgi, FrankenPHP and LiteSpeed. Researchers also showed they could read a worker’s memory without getting code execution, and that memory can hold passwords, tokens and API keys. In a Laravel app, that means the contents of your .env.
The fixed versions are PHP 8.5.12, 8.4.27, 8.3.36 and 8.2.35. Anything older than 8.2 is end of life and gets no fix at all.
How the PHP2Shell disclosure unfolded
The interesting part of this incident is how much happened before a patch existed.
| Date | What happened |
|---|---|
| 11 Aug 2026 | Security firm Calif, auditing PHP through the Patch the Planet programme, reports the first remote code execution. The root cause matches an out-of-memory problem the PHP team had already found in 2024. |
| 24 Aug | Wiz Research independently reports the same bugs. |
| 17 Oct | Amazon sees a first exploitation attempt on its honeypots, four days before the fix. |
| 20 Oct | Details leak. watchTowr confirms the remote code execution is real, and PHP announces an emergency release. |
| 21 Oct, 07:43 UTC | Cloudflare reports attacks against its customers. |
| 21 Oct, around 13:30 UTC | PHP releases the fixed versions and publishes the advisories. |
So attackers were probing days before anyone could patch, and the leak turned a coordinated disclosure into a race. That is the backdrop for every decision below.
My setup: small, isolated and up to date
Every project I run is a Laravel application. I make a point of moving each one to every new major Laravel release, mostly because I want the new features, and I upgrade PHP alongside it when I have the time. The oldest PHP version I run right now is 8.4, so every server was on a branch that was getting a fix.
Each project runs on its own VPS. No shared hosting, and no two clients on the same box. That paid off in three ways this week:
- Isolation. If one server is compromised, the damage stops at one application, not my whole client list.
- Independent PHP versions. Each server has its own PHP install, so I can patch and restart one without touching the others.
- Fast, per-server recovery. As soon as a patched build is available for a server’s PHP version, that server can go back online. Nobody waits for the slowest one.
I manage all of them through Ploi.io. It gives me one place to see every server, push updates to all of them and get notified when something needs attention. In a week like this, not having to SSH into each box one by one is the difference between minutes and hours.
Deciding to take everything offline
When the first details appeared there was no patch yet, so the question was not how to fix it. It was what each application could afford. For every app I asked two things: what data passes through it, and what happens to the business if it is offline for a day?
- The ERP integration. One client pushes ERP data through an application I built. That is sensitive business data, but the company keeps working without the app, and recovery is simple: once it is back online, a single sync job brings everything up to date.
- The other applications. Several handle personal information, but taking them offline for a day has no real business impact.
Both answers pointed the same way. Downtime was cheap everywhere, and a breach (business data, personal data, possibly secrets read straight from memory) was expensive everywhere. That made it an easy call. I was not the only one either: several Dutch sites went offline as a precaution on 21 October.
Do the impact assessment before you need it
I could decide quickly because I already knew how each app behaves when it is down and how it recovers. If you do not know that for your own apps, write it down now, while nothing is on fire.
What I did
- Told my clients first. One email to everyone: all applications go offline temporarily, with a link to a solid write-up of the incident so they could read for themselves why.
- Stopped PHP-FPM on every server. With FPM stopped, nothing executes PHP for web requests, so there is nothing for these bugs to reach. nginx simply answers with a
502. - Patched server by server. As the patched PHP 8.4 and 8.5 builds landed, I updated each server through Ploi, restarted every PHP process on it and brought it back online.
- Ran the ERP sync. Once the integration was back, one sync job caught up on everything that happened while it was down. That was the entire recovery.
Why stopping PHP-FPM, and not something else
There are a few ways to take a site offline, and not all of them help against PHP2Shell.
php artisan down does not protect you
Laravel’s maintenance mode runs inside PHP. The startup attacks happen while PHP is still parsing the request, before your application code, middleware or maintenance check runs. The same goes for authentication, CSRF checks and security packages.
Disabling the PHP handler in your web server is risky in a different way. Mattias Geniar tested it and found that Apache and nginx then serve .php files as plain text. A Laravel app limits the damage because only public/ is in the web root, but it is still not something you want. Stopping only PHP-FPM was safe in his tests, which is exactly what I did.
After patching: a Laravel server checklist
Installing the new package is only half the job. This is what I would go through on every Laravel server.
Confirm the PHP version that is actually running
The CLI and FPM are separate binaries, so check both, and restart FPM instead of reloading it.
terminal
# should print 8.4.27 or newer (8.5.12 or newer on PHP 8.5)
php -v
php-fpm8.4 -v
# a full restart, not a reload
sudo systemctl restart php8.4-fpm
Restart your queue workers, Horizon and Octane
Queue workers, Horizon and Octane are long-running PHP processes. They keep the old, vulnerable binary in memory until they restart, even after the package has been updated.
terminal
# queue workers finish their current job and exit; Supervisor starts them again
php artisan queue:restart
# Horizon
php artisan horizon:terminate
For Octane, restart the Octane process itself through Supervisor. Reloading the workers is not enough to pick up a new PHP binary.
Keep session upload progress off
PHP’s upload progress tracking is a confirmed path for CVE-2026-109619, and PHP suggests leaving it off after patching if you do not use it. Laravel uses its own session handling instead of PHP’s native sessions, so for most Laravel apps turning it off costs nothing. Set it in php.ini or the FPM pool, not with ini_set() or .user.ini, which apply too late.
/etc/php/8.4/fpm/conf.d/99-disable-upload-progress.ini
session.upload_progress.enabled = 0
terminal
# should say Off
php-fpm8.4 -i | grep session.upload_progress.enabled
Look for signs of an attack
If a server was online on an unpatched PHP version after 17 October, look for:
- POST requests with
PHP_SESSION_UPLOAD_PROGRESSin the form data, if your WAF or logging captures request bodies. - PHP workers crashing or restarting without a clear reason, or bursts of
500responses after unusual multipart requests. - A
php-fpmprocess starting a shell,curl,wgetor a similar tool.
None of these proves an attack, and not finding them does not prove you are safe.
Rotate your secrets if you were exposed
A memory read leaves no web shell or strange process behind, so you cannot rule it out from your logs. If a server was reachable on an unpatched PHP version, treat everything in its .env as leaked: database passwords, API tokens, mail credentials and the APP_KEY. Since Laravel 11 you can rotate the APP_KEY without logging everyone out or breaking encrypted values, by moving the old key to APP_PREVIOUS_KEYS.
.env
APP_KEY=base64:your-new-key
APP_PREVIOUS_KEYS=base64:your-old-key
What I am taking away from this
- Stay on supported PHP versions. A fix only helps if your branch gets one. Upgrading Laravel and PHP regularly always felt like optional housekeeping. This week it was the reason a patch was waiting for every server I run.
- Isolate. One app per server feels like overkill until the day it is not.
- Know your blast radius in advance. Data sensitivity and the cost of downtime decide how hard you react.
- Restart everything that runs PHP. An updated package means nothing while old workers are still running.
- Talk to clients early. A short, honest email with a credible source builds more trust than silence followed by an incident report.
Frequently asked questions
What is PHP2Shell?
PHP2Shell is the nickname for CVE-2026-109618 and CVE-2026-109619, two critical vulnerabilities in the PHP interpreter disclosed on 21 October 2026. Both are rated 9.2 under CVSS v4.0 and can allow unauthenticated remote code execution on servers that handle web requests with PHP.
Which PHP versions fix PHP2Shell?
PHP 8.5.12, 8.4.27, 8.3.36 and 8.2.35. Every release before those is vulnerable, and PHP versions older than 8.2 are end of life and receive no fix.
Is my Laravel application affected by PHP2Shell?
Yes, if it runs on a vulnerable PHP version. The bugs are in PHP itself, so your Laravel version does not matter and composer update does not fix it. Update PHP, then restart PHP-FPM, queue workers, Horizon and Octane.
Does php artisan down protect against PHP2Shell?
No. Maintenance mode runs inside your application, and the attacks can happen while PHP is still parsing the request, before any application code runs. To take a Laravel site offline safely, stop PHP-FPM or let the web server answer requests without passing them to PHP.
Do I need to rotate my secrets after patching?
If the server was reachable on an unpatched PHP version, yes. Researchers showed the bugs can be used to read PHP worker memory, which can contain the values from your .env file. Patching does not undo anything that already leaked.
Boring is the goal
The best thing I can say about this incident is that it was boring for me. It is the same idea I wrote about in why commits matter: when the groundwork is done, the stressful moments shrink to a checklist. Small, isolated servers, supported PHP versions and one dashboard to manage them turned a critical PHP vulnerability into an email, a few service restarts and one sync job.
