PHP2Shell: two critical PHP flaws and a complete guide to patching hosting, VPS and Docker

PHP2Shell is a reason to patch PHP now, even if nobody has touched your WordPress site, online store or internal application for months. Two vulnerabilities in the interpreter can, under certain conditions, allow unauthenticated remote code execution. Updating plugins or running composer update does not fix the interpreter.
The patches were released on 21 October 2026. This guide reflects sources checked on 23 October 2026, covering legacy PHP as far back as 5.4 through the current stable 8.5 branch, shared hosting, virtual machines and containers. Versions below are the first fixed releases for their particular source; keep installing subsequent security updates.
My approach would be to patch the branch the application already uses and replace its running processes first. Migrating an old application to modern PHP is the next job. Waiting for a complete rewrite is not a reasonable justification for leaving an exposed server unpatched.
What went wrong, and what an attacker could access
CVE-2026-109618 concerns out-of-memory handling: an interrupted request can leave invalid internal data that PHP subsequently processes during cleanup. CVE-2026-109619 concerns process state that outlives the request memory it refers to; a later request can use that stale state. Both have a CVSS 4.0 score of 9.2. See the first PHP security advisory and the second advisory.
The practical damage follows the PHP worker's permissions. An attacker could access database credentials, customer data, API keys, application files or writable storage that the worker can reach. They could disrupt service and use available credentials to reach other systems. This does not automatically grant root; weak isolation between applications and excessive permissions make the consequences worse.
Consider a forgotten PHP contact form running under the same account as the main store. Its simplicity and low traffic provide no security boundary. If that account can read the store's configuration, the exposure extends well beyond the form.
Who needs to act: FPM is not an exemption
PHP recommends updating every installation serving web requests, including Apache mod_php, PHP-FPM, CGI, FrankenPHP and LiteSpeed. An attack before application code executes has been demonstrated against mod_php. FPM exposure depends on the specific path and application; Nginx + FPM must not be treated as categorically safe. Exploit conditions differ across versions and configurations.
Affected PHP reaches back to 5.4, as also described in Docker's security notice. Inventory production, internet-accessible staging, admin tools, old subdomains and internal sites. An internal network limits access but does not replace a patch. A command-line script without web input does not automatically have the same attack surface; still include its packages and persistent workers in your inventory.
Which versions contain the fix
For standard releases from the PHP project, compare versions within the same branch:
| PHP branch | First fixed upstream release |
|---|---|
| 8.5 | 8.5.12 |
| 8.4 | 8.4.27 |
| 8.3 | 8.3.36 |
| 8.2 | 8.2.35 |
| 8.1, 8.0, 7.4 | Upstream end of life; minimal source backports and some vendor fixes are available |
| 7.3 through 5.4 | No new standard upstream release; availability depends on the vendor and build |
See the PHP release announcements. For people maintaining their own builds, there are source branches for PHP 7.4, PHP 8.0 and PHP 8.1. These require ownership of builds, extensions and regression testing. They do not restore general support for those branches.
A distribution package can retain an older PHP version while carrying the fix. Ubuntu 24.04, for example, fixes PHP in 8.3.6-0ubuntu0.24.04.13. Seeing “8.3.6” alone is insufficient. Equally, “8.0.30” by itself does not establish that a legacy installation is patched.
Shared hosting: what to do without root access
Check the PHP handler for every domain, subdomain and application in your hosting panel. Web requests, scheduled tasks and SSH can use different installations. If the provider offers a fixed release on your existing branch, select it and test the site. Test a major migration such as 7.4 to 8.5 on a copy first.
Send support a specific request:
Please confirm remediation of CVE-2026-109618 and CVE-2026-109619 for the PHP runtime actually serving our domains. Please provide the full package version or vendor build identifier, the patch source, and confirmation that all relevant PHP workers were restarted. If we remain on PHP 8.0 or an older branch, confirm the backport of both fixes and the remaining support period. Please also check separate PHP installations used by scheduled tasks and the control panel.
“WordPress is up to date” is not an answer. A selector showing “PHP 8.3” does not identify the patched build either. If the host cannot substantiate remediation, arrange a move to a patched handler or another host. Until a fix is available, restrict access before traffic reaches PHP or temporarily take the site offline. A maintenance plugin executed by vulnerable PHP is not a sufficient boundary.
Plesk also runs its own PHP engine for the panel. Its advisory lists panel fixes in 18.0.80 Update 11 and 18.0.81 Update 4. Patched Linux builds of PHP 7.4.33, 8.0.30 and 8.1.34 are dated 21 October while retaining the same base version. Update both the panel and its active handlers. For cPanel, DirectAdmin or managed LiteSpeed, follow that vendor's process: updating the system PHP may leave their separate binaries unchanged.
Your own VPS: establish what is actually running
Do not start by adding an arbitrary repository. First identify the OS, package origin, web handler, FPM pools, containers and applications. On Debian or Ubuntu:
cat /etc/os-release
php -v
php --ini
dpkg-query -W -f='${binary:Package} ${Version}\n' 'php*' 'libapache2-mod-php*'
systemctl list-units --all --type=service 'php*fpm*'
php -v describes the CLI binary found in your current path. Check the virtual host configuration and the socket receiving proxied requests as well. SSH might report 8.3 while the site runs 8.0. In a hosting panel, inspect the handler assigned to the particular domain.
For an HTTP check, use a temporary admin-only diagnostic endpoint showing just PHP_VERSION and PHP_SAPI, then delete it. A public phpinfo() page exposes unnecessary configuration. Even an HTTP version check cannot establish a distribution backport on its own: connect it to the running service, full package identity and patch source.
Before changing anything, verify recoverable backups of the database, files and configuration. A snapshot is useful, but without database consistency it is not a substitute for a database backup. With multiple application nodes, rotate them out of service while replacing workers.
Ubuntu and Debian: exact fixed package versions
This table applies to official distribution packages, not a PPA, Surý repository, Plesk installation or custom build.
| Operating system | Minimum fixed PHP package version | Source |
|---|---|---|
| Ubuntu 26.10 | 8.5.9-0ubuntu2.1 |
USN-8948-1 |
| Ubuntu 26.04 LTS | 8.5.4-0ubuntu1.5 |
USN-8948-1 |
| Ubuntu 24.04 LTS | 8.3.6-0ubuntu0.24.04.13 |
USN-8948-1 |
| Ubuntu 22.04 LTS | 8.1.2-1ubuntu2.28 |
USN-8948-1 |
| Ubuntu 20.04 LTS | 7.4.3-4ubuntu2.29+esm5 |
USN-8948-2, Ubuntu Pro |
| Ubuntu 18.04 LTS | 7.2.24-0ubuntu0.18.04.17+esm14 |
USN-8948-2, Ubuntu Pro |
| Ubuntu 16.04 LTS | 7.0.33-0ubuntu0.16.04.16+esm22 |
USN-8948-2, Pro + Legacy Support |
| Ubuntu 14.04 LTS | 5.5.9+dfsg-1ubuntu4.29+esm18 |
USN-8948-2, Pro + Legacy Support |
| Debian 13 trixie | 8.4.27-1~deb13u1 |
stable-security package |
| Debian 12 bookworm | 8.2.35-1~deb12u1 |
DLA-4848-1 |
Older Ubuntu releases need the appropriate active extended-support entitlement. Check pro status. A version appearing in this table does not mean every machine can obtain it with a normal apt upgrade.
The following example is for PHP 8.3-FPM. Substitute the installed branch, and proceed after a backup and impact review:
sudo apt update
apt-cache policy php8.3-common php8.3-fpm php8.3-cli
apt-mark showhold
sudo apt-get -s upgrade
sudo apt upgrade
sudo systemctl restart php8.3-fpm
systemctl status php8.3-fpm --no-pager
dpkg-query -W -f='${binary:Package} ${Version}\n' 'php8.3*'
Read the simulated transaction. If no fixed candidate appears, investigate a missing security repository, signature error, pinning, held packages, unsupported OS release or delayed architecture build. Do not disable signature verification. Cover every installed PHP branch and its extensions, not merely the php metapackage.
For Apache mod_php, restart Apache after updating (sudo systemctl restart apache2). For Nginx + FPM, restart FPM; restarting only Nginx does not replace PHP. Update and recycle persistent queue workers, Octane/FrankenPHP and other PHP services through their process manager. Clearing OPcache does not patch the interpreter binary.
Ondřej Surý's packages: a route for PHP 8.0 and older
Yes, Czech developer Ondřej Surý, a long-standing Debian PHP maintainer, has produced backports for legacy branches. Start with deb.sury.org, the packages.sury.org repository and Ubuntu's ondrej/php PPA. Follow the current repository README and the actual origin of your installed packages. Repository infrastructure changes can produce different version suffixes.
| Legacy branch | Backport revision to check |
|---|---|
| PHP 8.1 | 8.1.34-9 plus the distribution suffix |
| PHP 8.0 | 1:8.0.30-25 plus the suffix |
| PHP 7.4 | 1:7.4.33-31 plus the suffix |
| PHP 7.3 | 7.3.33-35 plus the suffix |
| PHP 7.2 | 7.2.34-66 plus the suffix |
| PHP 7.1 | 7.1.33-81 plus the suffix, including the subsequent multipart-limit fix |
| PHP 7.0 | 7.0.33-93 plus the suffix, including the subsequent multipart-limit fix |
| PHP 5.6 | 5.6.40-105 plus the suffix, including the subsequent multipart-limit fix |
One verified Ubuntu 24.04 PPA example is 1:8.0.30-25+ubuntu24.04.1+deb.sury.org+1. The direct changelogs for PHP 8.0, PHP 8.1 and PHP 7.4 identify both CVEs. The 1: prefix is part of the Debian version, not a typo.
Early tables circulating online need a correction. The initial PHP 5.6–7.1 backports lacked the multipart limit associated with CVE-2023-0662. The maintainer confirmed this and announced the follow-up revisions above on 22 October in issue #146. I would therefore not treat 5.6.40-104, 7.0.33-92 or 7.1.33-80 as the target state.
A published source package also does not guarantee that every binary architecture has finished building. The maintainer's ongoing announcement discusses this timing difference. What matters is the candidate offered by apt-cache policy for your OS and CPU, and the package actually installed. For PHP 7.2/7.3, check the published 7.2 sources and 7.3 sources and their changelogs too.
Do not install Ubuntu 24.04 packages directly on 20.04 or Debian. An older OS may need another supported source, such as Freexian PHP LTS, or migration. Confirm the exact OS/PHP combination covered by the service; a downloadable file is not a support commitment. PHP 5.4 and 5.5 are not covered by the Surý table above. Without a documented vendor fix, keep them off the public internet.
Alpine Linux and Docker: two different update paths
Alpine with PHP installed through apk: the official catalogue confirms 8.3.36-r0 for Alpine 3.23, x86_64, php83-fpm. This is not proof of availability on every Alpine release and architecture. Check your repositories and all related packages.
cat /etc/alpine-release
apk info -v | grep '^php'
apk update
apk policy php83 php83-common php83-fpm
apk upgrade --simulate
apk upgrade
This example assumes an administrative shell and PHP 8.3 installed with apk. Afterwards replace the FPM/Apache processes using the appropriate process manager. On an OpenRC host, inspect service names with rc-status; in a container, normally deploy a new image. Do not mix stable and edge repositories just to obtain one newer PHP package.
The official php:…-alpine image is different. PHP is built separately, typically under /usr/local; apk upgrade alone does not patch that binary. Similarly, apt upgrade inside an official Debian-based PHP image may not replace PHP. Inspect the image origin and Dockerfile; the official Docker PHP sources document how it is built.
Docker announced patched Official and Hardened Images on 22 October. An old pinned tag or digest does not repair itself. For a custom application image, change the base to a fixed release of the appropriate branch and variant, rebuild extensions, run tests and deploy the resulting image.
For a Compose service named php:
# Prebuilt image: first update its tag/digest in the configuration
docker compose pull php
docker compose up -d --no-deps php
# Custom image: first update FROM in the Dockerfile
docker compose build --pull --no-cache php
docker compose up -d --no-deps php
# Check the process and CLI inside the new container
docker compose ps php
docker compose exec php php -v
Choose the relevant deployment path rather than blindly running both. Your service may have another name. Verify an actual web request and the deployed image identity too; the CLI check is only one piece of evidence. docker restart keeps the old container's binary. On Kubernetes, update the image/digest and complete the rollout across all replicas; restarting Pods with the old digest is insufficient. For packaged Nextcloud, WordPress or another application image, obtain the fixed image from its supplier. Pulling an unrelated PHP image alongside it changes nothing.
Alpine and musl do not protect against a flaw in PHP. A new container on an unsupported host does not patch the host kernel either.
RHEL, Rocky, AlmaLinux, Remi and CentOS 7
On a supported distribution, identify the package origin and enabled stream first. System PHP, Remi, Software Collections and panel-managed PHP are separate installations.
cat /etc/os-release
rpm -qa 'php*' | sort
dnf repolist
sudo dnf upgrade --refresh --assumeno
Review the transaction, then run sudo dnf upgrade --refresh and restart the services actually serving the application. System FPM is commonly php-fpm, Apache httpd; Software Collections use their own service names. Check every active installation. For a vendor backport, compare the full RPM version with that vendor's advisory rather than just the upstream table.
Remi published PHP 8.2.35, 8.3.36, 8.4.27 and 8.5.12 on 22 October for Enterprise Linux 8+ and Fedora 43+, including Software Collections. Release-day messages saying Remi had no update are therefore outdated. That announcement does not cover CentOS 7 or automatically cover every end-of-life PHP branch.
CentOS Linux 7 stopped receiving updates on 30 June 2024, as the CentOS project states. Pointing repositories at Vault gives access to an archive, not new security fixes. Do not promise that yum update will fix such a server. Still older systems are at least as problematic.
For a legacy server, I would:
- Restrict access to PHP at the proxy/firewall or temporarily take the application offline.
- Establish whether a vendor offers a verified backport for that exact OS, PHP branch and architecture. Obtain the build identity and confirmation that both CVEs are covered.
- Provision a supported VPS, copy the application and database, and test extensions and permissions. Deploy supported PHP where compatible, or a temporarily maintained legacy runtime with a firm retirement date.
- Switch traffic and decommission the old server. Patching PHP alone does not repair unsupported OpenSSL, webserver or kernel components.
RHEL 7 with an appropriate paid extended-support arrangement is not equivalent to CentOS 7. Do not install EL8/EL9 RPMs directly on EL7. For applications tied to an old ionCube loader or proprietary extension, confirm compatibility with the supplier; changing PHP may require a new application or loader release.
Cloudflare proxy and WAF: an extra layer while you patch
Cloudflare can help only with requests that actually pass through it. The Free Ruleset changelog for 22 October identifies seven blocking rules covering both CVEs. This updated existing rule names and metadata, not their detection logic. The Managed Ruleset received equivalent metadata changes. Check their effective configuration in your zone, including Skip exceptions. A WAF neither patches PHP nor guarantees coverage of every attack variant.
For a typical website:
- Inventory every web hostname on the server: apex, www, API, admin, staging and old aliases in other DNS zones. Enable Proxied, the orange cloud, for web-facing A, AAAA and CNAME records. After moving DNS, check zone activation and preserve mail records. See proxy status.
- Use SSL/TLS Full (strict) with a valid origin certificate. It verifies the Cloudflare-to-server connection but does not prevent direct origin access. Full (strict) configuration.
- Close routes around the proxy. Restrict origin web ports to required traffic from Cloudflare's current IP ranges, covering IPv4 and IPv6. Handle SSH administration, monitoring and certificate renewal separately so you do not lock yourself out. Alternatively, use an outbound Tunnel with no public application ingress. Protecting the origin.
- Test the website, forms, uploads and integrations. From another network, also test the origin IP using the correct hostname/SNI: it must not bypass protection and execute PHP. A certificate error or the default virtual host's response alone does not establish this.
Every public web route to the protected server must pass through the proxy or be blocked. A forgotten DNS-only website, IPv6 address or alternative port can defeat the restriction. This does not mean proxying email: MX and TXT records are not proxied this way, and the ordinary web proxy does not cover SMTP or SSH. Mail on the same IP can also reveal the origin address. Hiding an IP is no substitute for a firewall. See exposed origin IPs. On shared hosting, the provider must arrange origin restrictions.
Keep internal work running with Access, Google sign-in or a VPN
For an employee-only system, I would prefer temporarily closing public access to shutting down the entire server. A public portal can serve a static maintenance page while staff continue using an SSO-protected admin interface. The proxy or web server must return maintenance responses before forwarding requests to PHP. A PHP maintenance page or the application's own login does not provide that boundary.
Cloudflare Access can require authentication in front of a website without a VPN client. Use the Google Workspace integration for corporate accounts and groups, or standard Google authentication for named individuals. Being able to sign in with Google does not automatically mean being authorized.
A practical setup for an internal admin interface:
- Connect the identity provider under Zero Trust → Integrations → Identity providers, following its setup guide. For Google, configure an OAuth client and the redirect for your Access organization.
- Under Access controls → Applications, create a Self-hosted application for the internal hostname. An Allow policy should admit only named employees or the intended corporate group. Avoid Everyone and broad Bypass policies. Set an appropriate session lifetime and require MFA. With Google, enforce MFA through your organization or use Access independent MFA; do not assume its integration reports the same MFA attributes as every other IdP. Access MFA options.
- Prefer protecting the entire internal hostname. Protecting only
/admincan miss login, upload and admin API endpoints. Disable public portions separately before PHP; an exposed vulnerable endpoint on the same server can threaten the protected admin too. - Validate the Access token at the origin. With Tunnel, enable Protect with Access, or have the origin validate tokens itself. Tunnel alone does not enable SSO, and allowing Cloudflare IPs alone does not prove a request passed your Access policy. Self-hosted application setup.
- Test anonymous access, an unauthorized Google account, an authorized employee and the direct origin. The first two must not receive application responses; the employee must be able to complete real work.
n8n jobs, monitoring and API clients cannot complete interactive login. Where supported, use a narrowly scoped Service Auth policy and an Access service token, stored as a secret with an expiry. Temporarily pause a public webhook that cannot authenticate rather than granting a broad Bypass.
A corporate VPN is another option: expose the application only through the private network/VPN, restrict network permissions and remove the public route. A VPN is not another login page inside public PHP. Both approaches reduce who can reach the vulnerable runtime; compromised authorized accounts or devices remain a risk. Patching and restarting every PHP worker are still required.
If you cannot deploy a fix today
There is no configuration-only substitute for the complete patch. The strongest temporary exposure reduction is a boundary before PHP: VPN access, a proxy IP allowlist or a static maintenance page. Check whether direct access to the origin bypasses the CDN.
Official guidance describes partial measures: administratively disable session.upload_progress.enabled and, on PHP 8.5, fatal_error_backtraces, then restart workers. They do not cover every path, and application-level settings may take effect too late. See the request-lifecycle advisory.
You may also see memory_limit=-1 suggested for the OOM issue. Do not copy that blindly onto shared hosting or a small VPS. It removes a memory-consumption safeguard without eliminating every allocation failure. It requires considered proxy limits and resource isolation; RLIMIT_AS/RLIMIT_DATA are not interchangeable substitutes. The OOM advisory explains the limitations.
A WAF, hidden PHP version, suppressed errors or disabled exec() does not prove a memory-corruption flaw has been removed. Assign an owner and a patch deadline to every temporary measure.
What completed remediation looks like
“The update command finished” is not enough. Check that:
- Every domain is mapped to its actual runtime and complete fixed package version or image digest.
- The patch comes from the correct distribution/vendor source and covers both CVEs; legacy Surý branches include the follow-up revisions.
- All FPM pools, Apache processes, containers and persistent PHP services run the patched interpreter, with no old replica left behind.
- Login, forms, uploads, checkout/payment, email, scheduled tasks and queues work where the application uses them.
- You review error rates and logs after deployment and remove diagnostic endpoints.
- Legacy PHP and unsupported operating systems have a concrete migration plan.
Include XML imports and extension-dependent workflows in testing. There is a SimpleXML regression report for PHP 8.4.27/8.5.12; check the vendor's current resolution and whether your application is affected. Returning to an exposed vulnerable runtime is not a sensible default rollback. Restrict traffic while deploying a compatible patched build instead.
If compromise is suspected, patching alone is insufficient. Isolate the server, preserve logs and relevant evidence, and inspect changed files, accounts and scheduled tasks. Recover from a trusted baseline and rotate credentials and tokens from a clean environment. Clean-looking logs do not prove that exploitation never happened.
The easiest operational mistake is patching the visible installation while overlooking three others. Keep a simple record: domain → server/container → PHP handler → package and source → process replacement → verification. It helps close this incident and makes the next PHP emergency much easier to handle.