Here’s a sentence nobody running a VPS wants to read: your PHP version might let a complete stranger run code on your server, without a username, without a password, and before your website’s code even starts. That’s what happened on October 21, 2026, when the PHP project shipped an emergency release for two critical flaws nicknamed PHP2Shell. If you manage your own VPS, nobody is going to patch this for you. This guide shows you how to patch PHP2Shell on a VPS, confirm the fix actually took, and spot the signs that someone got in first.
Updating WordPress won’t save you. Updating plugins won’t save you. The bug is in PHP itself — the runtime underneath WordPress, Joomla, Drupal, Magento, and Laravel. The only fix is a newer PHP, or a vendor package that backports the fix. The good news: checking takes two minutes, and patching is usually one package update plus a restart.
What happened in October 2026
The PHP security team had planned a coordinated disclosure for Wednesday, October 21, 2026, at 14:00 UTC, for two critical vulnerabilities, both rated CVSS v4.0 9.2. Then details leaked early — a post on Hacker News described two remote code execution flaws, and exploit code calling itself PHP2Shell started circulating before the embargo lifted. PHP shipped the emergency fixes on October 21 anyway: PHP 8.5.12, 8.4.27, 8.3.36, and 8.2.35, with an advisory urging all PHP users to update immediately.
The story kept moving. On October 23, PHP disclosed a third, related session-handling flaw, CVE-2026-115560, which the October 21 releases do not fix. By October 26, researchers at Trail of Bits said they had confirmed PHP2Shell exploits working against pre-patch PHP 8.2 through 8.5. As of October 28, Wordfence — which protects over 5 million WordPress sites — reported ongoing scanning and exploitation attempts across its network, while noting it was not aware of any confirmed WordPress compromise through PHP2Shell at that point. No confirmed compromises yet does not mean nobody is trying. It means the window for unpatched servers is still open.
On shared and managed hosting, your provider patches PHP — Hostinger, GoDaddy, and others announced patched managed fleets on October 22–23, and you should still confirm on your provider’s status page. On an unmanaged VPS, there is no provider patching your PHP. That job is yours, your developer’s, or your sysadmin’s. Nobody else is coming.
What PHP2Shell actually is: two CVEs, one nickname
PHP2Shell is not an official PHP project name — it’s the name the security community gave to the leaked exploit tool, and it stuck. Underneath it are two distinct bugs in PHP core. Both allow unauthenticated remote code execution, both are rated 9.2, and both live in the interpreter itself, not in any framework or CMS. That’s why no application update can fix them.
| CVE | What goes wrong | CVSS v4.0 |
|---|---|---|
| CVE-2026-109618 | RCE via out-of-memory corruption: when PHP runs out of memory mid-request, cleanup can treat partially initialised internal request data as valid | 9.2 (Critical) |
| CVE-2026-109619 | RCE via stale global state: internal state tied to a request can survive after its memory is freed, and a later request on the same worker can use it before it is reset | 9.2 (Critical) |
CVE-2026-109619 is the easier one to picture. PHP has a session upload progress feature, enabled by default since PHP 5.4, that lets a browser track a file upload. When a multipart POST request contains a PHP_SESSION_UPLOAD_PROGRESS field, PHP’s session handler processes it while parsing the upload — before your application script runs at all. An attacker who can send a crafted multipart request to any PHP endpoint can reach that early, pre-application code path with no credentials, and end up executing code as the PHP worker user.
CVE-2026-109618 is about what happens when memory runs out at exactly the wrong moment during request setup. You don’t need the memory internals to act on it — just this: neither bug cares what CMS you run, how strong your login page is, or how tidy your firewall looks. If PHP serves web requests on that box and the build is older than the fixed releases, the box is in scope.
One warning: exploit code claiming to demonstrate these flaws is circulating publicly, and researchers have warned that unverified exploit code is sometimes laced with malware. Don’t download and run random “php2shell” tools to “test” your server. Check your version, patch, and verify — that tells you everything a PoC would, without handing your server to a stranger’s payload.
Who is at risk (and who is not)
The PHP project’s advisory puts it simply: all PHP installations serving web requests are at risk. The risk is not identical everywhere — PHP says it is highest for Apache with mod_php, where it describes a demonstrated exploit that fires before any application code runs — but every common way of serving PHP is in scope.
| How PHP runs | At risk? | Notes |
|---|---|---|
| Apache + mod_php | Yes — highest risk | Demonstrated exploit fires during request startup, before app code |
| PHP-FPM (Nginx / Apache) | Yes | Session upload progress RCE validated against FPM; restart workers after patching |
| php-cgi, FrankenPHP, LiteSpeed (LSAPI) | Yes | Patch every SAPI on the box, not just the one you think you use |
| PHP CLI / cron only, no web requests | Lower | Advisory targets web-serving installs; still patch |
| No PHP at all (pure Node.js, Go, static) | No | Unless a forgotten PHP install hides on the same box — check, don’t assume |
That last row catches more people than you’d think. Plenty of “Node.js-only” servers still run PHP somewhere: a leftover WordPress install, phpMyAdmin, a hosting panel that installed PHP by default, or a Docker image nobody remembers. Before you declare yourself safe, look for PHP on the machine.
Version-wise, the line in the sand is clear: 8.5.12, 8.4.27, 8.3.36, and 8.2.35. Anything below the fixed release on its branch is affected. PHP 8.1 and older receive no releases from the PHP project at all — the project published minimal source backports for PHP 7.4, 8.0, and 8.1, but most people on those branches depend on their Linux distribution or an LTS vendor backporting the fix, or they need to upgrade. If you’re still on PHP 8.2, note that its security support ends December 31, 2026 — patch now, and plan the move to 8.3 or 8.4 while you’re in there.

Check if your VPS is affected in 2 minutes
Start with two commands. The first shows which PHP the command line sees; the second shows when that PHP build was compiled — and the build date is the check that settles arguments, for reasons we’ll get to below.
php -v
php -i | grep -i "build date"
Compare the version against the fixed releases for your branch: 8.3.35 or lower on the 8.3 branch is below the fix; 8.3.36 or higher is at or above it, and the same logic applies to 8.4.27, 8.5.12, and 8.2.35. But don’t stop at the CLI. The PHP your website uses can be a completely different binary from the one your shell finds first, especially on panel-managed servers with several PHP versions installed. Check the web-facing PHP too: a temporary phpinfo() page (check it, then delete it — never leave it public), your hosting panel’s PHP version screen, or WordPress under Tools → Site Health → Info → Server.
WordPress users, one caveat: Site Health checks the branch (like “8.3”), not the full patch number, so a green Site Health result does not prove you’re patched. You need the full version — 8.3.36, not just 8.3 — and ideally the build date.
Next, find every PHP on the box, not just the first one in your PATH:
# Is any PHP process serving requests right now?
ps aux | grep -E "php-fpm|php-cgi|apache2|httpd" | grep -v grep
# PHP installed through the system package manager
apt list --installed 2>/dev/null | grep -i php
# or on RHEL-family systems:
dnf list installed 2>/dev/null | grep -i php
# PHP installed by aaPanel / BaoTa, outside the package manager
ls /www/server/php/ 2>/dev/null
If the process check returns nothing and you don’t need PHP, you can stop here. If it returns something, every PHP it finds needs the same treatment: check its version, patch it, restart it. A patched FPM pool sitting next to an unpatched mod_php install is still an open door. Also check whether session upload progress is on — it’s enabled by default, so assume yes unless you’ve turned it off:
php -i | grep -i "upload_progress"
How to patch PHP2Shell on a VPS: Ubuntu and Debian
The principle is identical however PHP got onto your server — get the fixed build, then restart every PHP worker so the old binary leaves memory. On Ubuntu/Debian, a full package upgrade pulls the fixed build once your repository carries it:
sudo apt update
sudo apt upgrade
To upgrade only PHP and leave the rest of the system untouched for now, target your branch explicitly — replace 8.3 with your version:
sudo apt update
sudo apt install --only-upgrade 'php8.3*'
On Ubuntu with the Ondřej Surý PPA, the same apt upgrade path applies. On Debian, the security repository may carry a backported fix that keeps the upstream version number — which is why the build-date check in the verification section matters more than the version string alone.
Now restart PHP. This is the step people skip, and the most common reason a server stays vulnerable after a “successful” patch: installing a new package does not restart a running PHP-FPM master process or Apache. Restart both, adjusting the version number to yours:
sudo systemctl restart php8.3-fpm
sudo systemctl restart nginx
# or, if you run Apache with mod_php / FPM:
sudo systemctl restart apache2
If you run multiple PHP versions, restart every FPM pool. An old worker still running the old binary in memory is still exploitable, no matter what php -v says on the command line. Take a snapshot before patching if your provider offers one — a PHP security update is low-risk, but a snapshot turns a bad afternoon into a five-minute rollback. Our new VPS setup checklist covers snapshots and backups as part of getting a server into a sane state in the first place.

Patching on RHEL, hosting panels, and Docker
The patch is the same everywhere; the package manager changes, and panels add a layer that can quietly bypass everything you just did at the command line.
RHEL, AlmaLinux, and Rocky Linux
sudo dnf update
# or, PHP only:
sudo dnf update 'php*'
sudo systemctl restart php-fpm
sudo systemctl restart httpd nginx
Whether PHP comes from AppStream modules or a repo like Remi, dnf update is the path, and the same restart rule applies: restart FPM and your web server, then verify the web-facing version, not just the CLI.
cPanel, Plesk, and CloudLinux
With cPanel, PHP updates go through EasyApache 4 in WHM, not through apt or dnf. Run the EasyApache update and apply the PHP profile. In Plesk, use Tools & Settings → Updates and Upgrades, then confirm the PHP version assigned to each subscription. On CloudLinux, the Alt-PHP packages received fixes too — but early CloudLinux patches did not bump the displayed version number, so check the build date in PHP Info (October 21, 2026 or later) rather than trusting the version string alone.
aaPanel / BaoTa: the trap
This setup deserves its own warning, because apt upgrade silently does nothing here. aaPanel installs its PHP versions under /www/server/php/, outside the system package manager. Running apt upgrade updates the system PHP, if any — and leaves the panel’s PHP, the one actually serving your sites, completely untouched. On aaPanel, update PHP from the panel itself: App Store → find your installed PHP version → update it, or reinstall that PHP version from the panel so it pulls the fixed build. Then restart PHP from the panel and verify through a phpinfo() page on a real site.
Docker and containerised PHP
If PHP runs in a container, patching the host does nothing. The fix lives in the image. Rebuild from a base image at or above the fixed release — a php:8.3 image published after October 21, 2026 — with --no-cache so Docker doesn’t reuse a stale layer, then redeploy every container running the old image and check the running container, not just the image tag:
docker build --no-cache -t myapp:latest .
docker compose up -d --force-recreate
docker exec myapp-php php -v
Scan for forgotten images too: a staging container or an old tag that still gets deployed on rollback can quietly reintroduce the vulnerable build. If you run AI apps in Docker on the same VPS, our guide to running AI apps with Docker on a VPS covers the container workflow — the patching principle is the same: rebuild, redeploy, verify the running container.
The stopgaps that are not a fix
PHP’s advisory lists configuration changes that reduce exposure to specific attack paths if you cannot update immediately. Read the emphasis carefully: they reduce exposure. They do not make an installation safe. Only an update is a complete solution. If you apply a stopgap, treat it as buying hours, not as being done.
| Stopgap | What it does | What it does NOT do |
|---|---|---|
session.upload_progress.enabled=0 + restart all PHP workers |
Blocks the confirmed session upload progress path for CVE-2026-109619 | Does nothing for CVE-2026-109618; does not close every variant of the session path |
memory_limit=-1, set so applications cannot override it |
Reduces exposure to the out-of-memory path for CVE-2026-109618 | Not a complete mitigation; removing the memory limit has its own availability risks |
fatal_error_backtraces=0 (PHP 8.5) |
Reduces exposure to a specific path for CVE-2026-109619 on PHP 8.5 | Only relevant on 8.5; not a fix on its own |
To apply the upload-progress stopgap, set it in php.ini — not in an application, which runs too late, since the vulnerable code fires before your code does — and restart every PHP worker:
; in php.ini
session.upload_progress.enabled=0
sudo systemctl restart php8.3-fpm
sudo systemctl restart apache2
Researchers at Datadog reported on October 26 that disabling session upload progress does not fully protect a server, and PHP’s advisory says the same in different words. A firewall rule blocking oversized multipart uploads can help as an additional stopgap. None of it replaces the update. If someone tells you they “fixed PHP2Shell” by flipping one ini setting, they haven’t — they’ve narrowed one path and left the other open.
Verify the patch actually took
Patching and being patched are different states. Run through this checklist after every update, and don’t mark the job done until all of it passes:
- Check the version the website uses, not just the CLI. Create a temporary
phpinfo()page, load it, read the version and build date, then delete the file immediately — a publicphpinfo()page hands attackers your configuration. - Check the build date. Some hosts slipped the fix into the PHP version you already run instead of bumping the number — a site can legitimately show PHP 8.5.11 after patching. What proves the fix is in is a build date of October 21, 2026 or later. If the build date is still older, the update didn’t reach that binary: look for a second PHP installation, a missing repository, or a service you forgot to restart.
- Confirm every worker restarted. If FPM restarted but Apache still runs old mod_php workers, those workers are still vulnerable. When in doubt, restart both and check process start times.
- Re-check upload progress. Confirm it’s still off in the web-facing
phpinfo()if you disabled it, and consider leaving it off — PHP has suggested keeping it off until the next regular release (see below), and if you don’t need upload progress tracking, it costs you nothing. - Test the site. Load the homepage, log in, submit a form, upload a file. A security update shouldn’t break a healthy site, but find out now — with a snapshot to roll back to — not from a visitor’s email tomorrow.
If you’re hardening the box while you’re in there, our VPS security checklist walks through the rest: firewall rules, SSH hardening, brute-force protection, and automatic updates so the next emergency release doesn’t wait on you remembering to check.
The third flaw: CVE-2026-115560
On October 23, 2026, PHP disclosed a third remote code execution path in the same session upload progress feature: CVE-2026-115560. The October 21 releases do not fix it, and there was no patched release for it at disclosure — PHP said it would publish a full advisory with the next regular bugfix release, handling it in the normal release cycle rather than as another emergency.
Before you panic, read the scope. PHP describes this flaw as affecting only applications that switch the session serialise handler from php_binary or php_serialize to php using ini_set() before calling session_start(), while session upload progress is enabled. PHP considers that setup unlikely in practice, and no exploitation had been reported at disclosure. It’s a narrower, more conditional bug than the first two.
PHP’s interim guidance is to disable session upload progress (session.upload_progress.enabled=0) until the next release ships — the same stopgap above. If you already disabled it and patched to the October 21 releases, PHP’s position is that you need do nothing more for this flaw right now. One security firm that validated the issue reported that disabling upload progress reduces exposure but does not close every path, so apply the fixed release as soon as PHP ships it rather than treating the stopgap as a permanent all-clear. Because this story is still moving, check PHP’s 2026 news archive for the newest release before you act — a newer bugfix release may have landed since this article was written.
Signs your server was already hit
Patching closes the door. It doesn’t undo anything that walked through it while the door was open. If your server was unpatched and internet-facing between October 20 and your patch date, spend fifteen minutes looking for signs of a break-in. Start with the logs: POST requests containing PHP_SESSION_UPLOAD_PROGRESS, especially with large multipart bodies, and “allowed memory size exhausted” errors during request startup, before your application logs anything. Then look for things that shouldn’t exist:
# PHP files created or modified since the disclosure
find /var/www /www/wwwroot -name "*.php" -newermt "2026-10-20" -type f 2>/dev/null
# Processes running as the web user that aren't PHP-FPM
ps -u www-data -o pid,etime,cmd 2>/dev/null | grep -v php-fpm
# Unexpected cron jobs
crontab -l; ls -la /etc/cron.d /var/spool/cron/crontabs 2>/dev/null
# Outbound connections that aren't SSH or web traffic
ss -tnp state established | grep -vE ":(22|80|443) "
Files you didn’t touch, unknown processes running as the web user, new cron jobs, or outbound connections to unfamiliar addresses are all reasons to treat the server as compromised. And if it is, don’t try to clean it in place by deleting one suspicious file. A successful attack can plant a webshell and move laterally to databases, stored secrets, and every other site the same server hosts. Rebuild from a clean image, restore content from a known-good backup taken before October 20, rotate every secret that lived on that box — database passwords, API keys, admin passwords — and patch before the rebuilt server touches the internet. If you’re on a managed host and suspect compromise, open a ticket and say so explicitly.
FAQ
Does updating WordPress fix PHP2Shell?
No. WordPress.org has said these vulnerabilities are not specific to WordPress and affect any server hosting a PHP application, and that no WordPress update is required or planned for them. Clicking “Update” in your dashboard updates WordPress; it does not update PHP. Only a PHP update — or a vendor package containing the fix — closes these flaws.
I’m on shared hosting. Do I need to do anything?
Probably not directly, but verify rather than assume. Most shared and managed hosts patched their fleets on October 21–23. Check your provider’s status page, and confirm your site’s PHP version and build date through your hosting panel or Site Health. If your host hasn’t confirmed a patch, ask them in a ticket, with the CVE numbers. On a VPS you manage yourself, the job is yours.
Does disabling session upload progress fix it?
No. It blocks one confirmed attack path for CVE-2026-109619, and PHP recommends keeping it off as a stopgap, but it does nothing for CVE-2026-109618 and does not close every variant. The PHP security team is explicit: updating is the only complete fix.
Which PHP version should I be on after patching?
At minimum, the first fixed release on your branch: 8.5.12, 8.4.27, 8.3.36, or 8.2.35 — or a vendor package confirmed to contain both fixes (check the build date if the version number didn’t change). If you’re on PHP 8.1 or older, there is no official PHP release with the fix; upgrade to a supported branch or confirm your distribution’s backport. And if you’re on 8.2, plan the move to 8.3 or 8.4 now, since 8.2 security support ends December 31, 2026.
Will patching break my site?
Almost certainly not. These are security releases on the same minor branch — 8.3.35 to 8.3.36, for example — not feature upgrades. Still, snapshot first, patch, restart, and test the site (including uploads and logins) before you call it done.
What about the October 2026 Windows Server RDP flaws?
Different bugs, same month, same lesson. October 2026 also brought critical RDP vulnerabilities in Windows Server, including an unauthenticated RCE in Remote Desktop Gateway (CVE-2026-70187). We covered those patches, KB numbers, and verification steps in Patch Windows Server Now: October 2026’s Critical RDP CVEs, and the separate RDP breakage from the same updates in How to Fix RDP Error 0xC000000D After the October 2026 Windows Update.
The bottom line
PHP2Shell rewards speed and punishes assumptions: two critical, unauthenticated RCE flaws in PHP core, publicly discussed before the fix shipped, with scanning observed within days. The fix has been out since October 21. If you run your own VPS, checking your PHP version takes two minutes, patching takes one package update and a restart, and verifying takes a phpinfo() check and a look at the build date. There is no good reason for a VPS you manage to still be running a pre-fix build a week later.
Run php -v, compare against 8.5.12, 8.4.27, 8.3.36, or 8.2.35, update, restart every PHP worker, and verify the web-facing version and build date. Leave session upload progress off if you don’t need it, watch for the CVE-2026-115560 fix in PHP’s next release, and if the server was exposed during the window, check the logs before you assume you’re clean. Patching is a ten-minute job. Explaining a compromised server to your users is not.


