On October 21, 2026, the PHP project shipped an emergency security release fixing two critical remote code execution flaws, CVE-2026-109618 and CVE-2026-109619. Security researchers quickly gave the pair a nickname: PHP2Shell. Both are rated CVSS v4.0 9.2, both need no authentication, and exploitation attempts have been observed in the wild since the patches came out, with some activity dating back to before the fix was public.

This post explains how PHP2Shell works at a technical level, shows you how to check whether your server is exposed, and gives you the exact temporary mitigations to apply if you cannot patch right now. One warning up front, repeated below: PHP itself calls these mitigations incomplete. Updating to a fixed release is the only complete fix.

Prerequisites

  • A Linux server (Ubuntu 22.04/24.04 used here) running PHP 8.2, 8.3, 8.4 or 8.5 that serves web requests.
  • SSH access with sudo, and a text editor you are comfortable with.
  • About 20 minutes. Patching itself takes five; understanding what you are patching is the point of this guide.

Step 1: Check which PHP version your web server actually runs

Run this on the server to see the CLI version:

php -v

This prints the command-line PHP build. It tells you which branch you are on, but it does not prove your web workers run the same build.

Now check what the web server itself reports. If you have a phpinfo() page, open it in a browser and read the version at the top. If you run PHP-FPM, confirm the pool version:

php-fpm8.4 -v 2>/dev/null || php-fpm -v

This shows the FPM binary version. After any update you must check the version through the web server, not just with php -v, because running workers keep the old code until they are restarted.

Step 2: Understand CVE-2026-109618 (RCE via out-of-memory corruption)

This is the deeper of the two flaws (GitHub advisory GHSA-w8hw-w9fx-j79w). When PHP runs out of memory mid-request, it does not shut down gracefully. It uses longjmp() to bail out instantly, jumping to an error handler far up the call stack.

The problem: if the out-of-memory hit lands while request memory is only partly initialized, the shutdown and cleanup code that runs afterwards reads that half-built state as if it were valid. Corrupted internal state plus attacker-influenced request data equals a path to remote code execution running as the web worker user.

An attacker does not need a memory-hungry page on your site to trigger this. The advisory lists ways to force the out-of-memory condition during request parsing itself:

  • Multipart request bodies with no usable Content-Length, slipping past post_max_size checks.
  • Multipart parts with unlimited headers.
  • Empty file fields that do not count toward max_file_uploads.
  • Deeply nested field names that blow up parser memory.

Check your current memory limit, one of the levers involved in the temporary mitigation:

php -i | grep -i "^memory_limit"

This prints the effective memory_limit for the CLI SAPI. Your FPM or Apache SAPI can differ, so repeat with php-fpm -i or a phpinfo() page for the web SAPI.

Step 3: Understand CVE-2026-109619 (RCE via stale global state)

The second flaw (GHSA-6wmf-4rv5-p3rp) is a use-after-free in global engine state. After a fatal error, memory that has already been released can still be referenced, and a later request handled by the same worker can end up using that freed memory. With carefully shaped requests, that becomes code execution too.

One confirmed exploitation path on PHP-FPM runs through session upload progress, the feature that lets a progress bar track a file upload via session.upload_progress.enabled. That is why disabling upload progress is one of the official temporary mitigations. Check whether it is on:

php -i | grep -i "upload_progress.enabled"

This shows whether session upload progress tracking is enabled. If it reads On or 1, the confirmed FPM attack path is open on your server until you patch or mitigate.

Step 4: Assess your exposure honestly

PHP says every installation that serves web requests is affected: PHP-FPM, Apache with mod_php, php-cgi, FrankenPHP and LiteSpeed. But the risk is not equal everywhere.

Apache with mod_php is the worst case. PHP reports a demonstrated exploit that triggers the attack before any application code runs. That single fact has a big consequence: a WAF rule, a WordPress security plugin, or an admin password change cannot reliably stop it, because the attack can land before your application ever gets a say.

On PHP-FPM, code execution was confirmed for CVE-2026-109619 through session upload progress, and the code path for CVE-2026-109618 is present. Find out which SAPI you run:

apache2ctl -M 2>/dev/null | grep -i php; ps aux | grep -E "php-fpm" | grep -v grep | head -2

The first command shows whether a PHP module is loaded in Apache (mod_php). The second shows running PHP-FPM workers. If the first prints a php_module line, you are on mod_php and should treat this as maximum urgency.

Step 5: Patch, the only complete fix

The fixed releases are PHP 8.5.12, 8.4.27, 8.3.36 and 8.2.35, all shipped October 21, 2026. PHP 8.1 and older get no official release; you need a vendor backport or an upgrade to a supported branch. (PHP describes the core change in its ChangeLog as "Avoid state corruption via bailout.")

On Ubuntu with the widely used Ondřej Surý PPA, update the branch you run. Example for PHP 8.4 with FPM:

sudo apt update && sudo apt install --only-upgrade php8.4-fpm php8.4-common

This pulls the newest 8.4 packages, which include the security fix, without touching unrelated software. Adjust the version numbers if you run 8.2, 8.3 or 8.5.

Now restart every PHP worker, because running processes keep the old vulnerable code:

sudo systemctl restart php8.4-fpm
sudo systemctl restart apache2

The first command restarts the FPM pools; the second restarts Apache (needed if you use mod_php, and harmless otherwise). Skip this step and the patch is not actually live.

Step 6: Apply temporary mitigations if you cannot patch yet

If patching has to wait, PHP published mitigations that reduce exposure. They are explicitly incomplete. Treat them as a stopgap measured in hours or days, not a fix.

Create a drop-in config so the mitigation is easy to remove later. For PHP 8.4 FPM:

sudo tee /etc/php/8.4/fpm/conf.d/99-php2shell-mitigation.ini > /dev/null <<'EOF'
; Temporary PHP2Shell mitigation - REMOVE after patching to 8.4.27+
session.upload_progress.enabled=0
EOF
sudo systemctl restart php8.4-fpm

This writes a single override file that disables session upload progress, closing the confirmed FPM attack path for CVE-2026-109619, then restarts FPM so it takes effect. On PHP 8.5 only, also add fatal_error_backtraces=0 to the same file; that directive does not exist on older branches.

For CVE-2026-109618, PHP suggests setting memory_limit=-1 combined with real memory limits at the host or container level, so the OOM bailout path cannot be reached from inside PHP while the machine itself stays protected:

sudo tee -a /etc/php/8.4/fpm/conf.d/99-php2shell-mitigation.ini > /dev/null <<'EOF'
memory_limit=-1
EOF
sudo systemctl restart php8.4-fpm

This appends the memory directive to the same drop-in and restarts FPM. Only do this if the server or container has its own memory cap (systemd MemoryMax, Docker --memory, or similar); without one, a runaway script could consume all host RAM.

Finally, block multipart uploads at the reverse proxy on sites that never accept file uploads. For Nginx, inside the server block:

# Temporary: reject multipart uploads where the app has no upload feature
if ($content_type ~* "multipart/form-data") {
    return 403 "Uploads disabled";
}

This returns 403 for any request whose content type is multipart form data, cutting off the main attacker-controlled OOM vectors at the edge. For Apache 2.4, the equivalent is:

<If "%{CONTENT_TYPE} =~ /multipart\/form-data/">
    Require all denied
</If>

This denies the same requests at the Apache layer. Scope either snippet narrowly if parts of the site legitimately accept uploads; a blanket block will break real upload forms.

Step 7: Verify it worked

First, confirm the patched version is what the web server serves, not just the CLI:

curl -s https://your-domain.example/php-version-probe.php

This should be a tiny page containing <?php echo PHP_VERSION; that you delete afterwards. The version it prints must be 8.5.12, 8.4.27, 8.3.36 or 8.2.35 (or newer). If it still shows the old version, a worker was not restarted; repeat Step 5.

Then confirm the mitigations are active in the web SAPI:

php-fpm8.4 -i 2>/dev/null | grep -Ei "upload_progress.enabled|^memory_limit"

This prints the effective values for the FPM binary. Expect upload_progress.enabled => 0 and memory_limit => -1 if you applied Step 6. Remember to remove the drop-in file once patched; mitigations are not a permanent configuration.

Common issues and fixes

1. php -v shows the new version, but the site still reports the old one. The web workers were not restarted. Run sudo systemctl restart php8.4-fpm and sudo systemctl restart apache2, then re-check through the browser, not the CLI.

2. The mitigation ini file has no effect. You edited the wrong SAPI directory. FPM reads /etc/php/8.4/fpm/conf.d/, CLI reads /etc/php/8.4/cli/conf.d/, and Apache mod_php uses the Apache2 SAPI directory. Put the drop-in in the directory matching how PHP serves your site.

3. File upload progress bars stopped working. Expected. Disabling session.upload_progress.enabled removes the feature those bars rely on. It is a temporary trade-off; restore it after patching.

4. The proxy multipart block broke legitimate uploads. The block is intentionally broad. Move it into a location that has no upload feature, or exempt the upload endpoint, rather than removing protection everywhere.

5. You run PHP 8.1 or older and there is no new version to install. The PHP project ships no official fix for these branches. Ask your OS or LTS vendor whether they backported the patch (some did), and plan an upgrade to 8.2+ immediately. Do not assume a distro package is safe just because its version number is high; vendors backport without bumping to the upstream fixed number.

One more thing: the October 23 session advisory

On October 23, PHP published a related but lower-risk advisory, CVE-2026-115560, about session handling. It needs a separate check from the PHP2Shell pair, and the mitigation overlaps with the upload-progress setting above. Patching to the October 21 releases plus reviewing the session advisory covers both.

Conclusion

PHP2Shell is serious because it sits below your application: on mod_php the exploit can fire before your code runs, so no plugin or WAF rule is a substitute for the patch. Check your version through the web server, update to 8.5.12, 8.4.27, 8.3.36 or 8.2.35, restart every PHP worker, and use the temporary mitigations above only as a bridge while you patch.

If server patching and hardening is eating your team's time, XpertBees handles exactly this: emergency patching, PHP upgrades, and server hardening for production workloads.