PHP2ShellCVE-2026-109618, CVE-2026-109619: critical RCE, exploited in the wild.Read the advisory
Fixes

Patch PHP2Shell, then restart, rotate and re-scan.

Pick your platform, SAPI and PHP branch to get the exact upgrade, restart and verify commands, with the target version filled in from the live php.net data. Then apply the interim mitigations and work the after-patch checklist.

There is no complete mitigation. Updating is the only safe choice.
These two bugs were exploited in the wild before the patch shipped. Upgrade to the fixed release for your branch, restart every worker, then assume secrets may have leaked.
Platform
Server API (SAPI)
Target PHP branch
Target: PHP 8.4.27 · Ubuntu (ondrej/php PPA) · PHP-FPM

Upgrade

  1. 1
    Refresh the package lists
    Refresh the package lists
    sudo apt-get update
  2. 2
    Upgrade the installed PHP 8.4 packages
    Upgrade the installed PHP 8.4 packages
    sudo apt-get install --only-upgrade 'php8.4*'

    --only-upgrade touches only php8.4 packages that are already installed. The ondrej/php PPA ships the fixed build; if apt reports no candidate, confirm the PPA is enabled and the fixed version is visible with apt-cache policy php8.4-fpm.

Restart

Restart (PHP-FPM)
sudo systemctl restart php8.4-fpm

Verify

Confirm the fixed version is live
php8.4 -v
php8.4 -r 'echo PHP_VERSION, PHP_EOL;'

The version a browser sees can differ from the CLI; confirm every SAPI reports 8.4.27 or newer.

  • Confirm the reported version is 8.4.27 or newer.
  • If several PHP branches are installed, repeat for each; update-alternatives --list php shows which binary php resolves to.

Sources:launchpad.net/~ondrejphp.net/archive

Interim: mitigations while you roll out

These reduce exposure while you roll out the upgrade. php.net is explicit that they are not complete: "There are no complete mitigations, updating is the only safe choice." Apply them as php_admin_value / php_admin_flag (FPM pool) or php_admin_value (Apache) so applications cannot override them, then restart every worker. Remove the ones you do not otherwise want once every worker runs the patched build.

Remove the memory limit (make OOM less reachable)

FPM pool (/etc/php/8.4/fpm/pool.d/www.conf) or Apache vhost

Remove the memory limit (make OOM less reachable)
; PHP-FPM pool
php_admin_value[memory_limit] = -1
  • The primary configuration mitigation, and not a complete one. Set it so applications cannot override it and restart every worker.
  • With no limit a single request can use all memory; watch memory and put a sane limit back once you have patched.

Sources:github.com/phpphp.net/archive

Keep request bodies small

FPM pool or php.ini

Keep request bodies small
php_admin_value[post_max_size] = 8M
php_admin_value[upload_max_filesize] = 2M
  • Keep it conservative: 8M without uploads, 32M if uploads are needed. Keep upload_max_filesize below post_max_size.

Sources:github.com/phpphp.net/archive

Reduce input nesting

FPM pool or php.ini

Reduce input nesting
php_admin_value[max_input_nesting_level] = 16
  • The advisory recommends reducing it below the default of 64, and bounding input variables, multipart parts, headers per part and uploads.

Sources:github.com/php

Disable session upload progress

php.ini, FPM pool, or server config applied before request parsing

Disable session upload progress
php_admin_flag[session.upload_progress.enabled] = off
  • Set it where it applies before request parsing, then restart every worker. Do not rely on ini_set() or .user.ini.
  • This blocks the confirmed session paths, not every variant. Keep it off after patching if you do not use the feature.

Sources:github.com/php

Disable fatal error backtraces (PHP 8.5 only)

php.ini or FPM pool

Disable fatal error backtraces (PHP 8.5 only)
php_admin_value[fatal_error_backtraces] = 0
  • PHP 8.5 only. Removes the producer used by the confirmed RCE case. Restart every worker.

Sources:github.com/php

Remove per-request INI that emits startup diagnostics

.user.ini, .htaccess php_value, FPM pool php_value, PHP_VALUE/PHP_ADMIN_VALUE

Remove per-request INI that emits startup diagnostics
; Remove or fix settings that emit a diagnostic before request startup:
;   auto_detect_line_endings = 1
;   report_memleaks = 0        (8.5)
;   legacy assert.* settings   (8.3+)
;   legacy session settings    (8.4+)
;   invalid memory_limit or date.timezone values
  • These are the pre-startup diagnostic triggers behind CVE-2026-109619. Turning off error_reporting or display_errors, or using @, does not help.
  • Verify the exact deprecated names against php.net's migration and deprecation notes for your branch.

Sources:github.com/php

Enforce request limits at the web server / proxy

nginx / Apache / proxy configuration

Enforce request limits at the web server / proxy
# nginx example
client_max_body_size 8m;
client_body_buffer_size 16k;
large_client_header_buffers 4 8k;
  • Bound decoded request size, input field count, multipart part count, header count and nesting depth, including requests with no usable Content-Length.
  • If the application does not use multipart/form-data, block it at the front end.

Sources:github.com/php

Do NOT use RLIMIT_AS / RLIMIT_DATA as a substitute

systemd unit, ulimit, container limits

Do NOT use RLIMIT_AS / RLIMIT_DATA as a substitute
# Avoid: LimitAS / ulimit -v as a mitigation
# A container or cgroup memory.max is an availability boundary only.
  • RLIMIT_AS / RLIMIT_DATA make mmap() fail, which triggers the vulnerable longjmp() path.
  • A container or cgroup memory.max limit bounds availability; it does not stop the bug.

Sources:github.com/php

Turn on error logging to find reachable fatals

startup php.ini (not per-request)

Turn on error logging to find reachable fatals
log_errors = On
error_reporting = E_ALL
error_log = /var/log/php/error.log
  • Set these in startup configuration to surface warnings and fatal errors an attacker could reach in callbacks, uploads, imports and cleanup. An empty log is not proof of safety.
  • Consider pm.max_requests = 1 if you can afford the performance cost.

Sources:github.com/php

After you patch

Patching is step one.

An upgrade only protects new processes, and it does not undo a break-in that already happened. Work through this once every worker runs the fixed build.

Restart every PHP worker and web server

The patched binary only loads in fresh processes. PHP-FPM pools, Apache mod_php, LiteSpeed lsphp, FrankenPHP workers, php-cgi processes and queue workers (Horizon, Supervisor, long-lived cron children) that started before the upgrade still run the old, vulnerable code. Restart them all and confirm start times are newer than the upgraded binary.

Restart every PHP worker and web server
sudo systemctl restart php8.4-fpm || sudo systemctl restart php-fpm
sudo systemctl restart apache2 || sudo systemctl restart httpd
php artisan queue:restart

Sources:php.net/archive

Rotate secrets that may have leaked

CVE-2026-109619 includes an unauthenticated memory-disclosure path. On any host that was internet-facing before you patched, treat secrets readable by the web worker as potentially exposed and rotate them: database passwords, APP_KEY / app secrets, third-party API keys, OAuth client secrets, mail credentials, and any crypto or Solana private keys, seed phrases or mnemonics in the environment or on disk. Revoke the old values, do not just add new ones.

Sources:github.com/phpphp.net/archive

Scan for signs of compromise

Both CVEs were exploited in the wild before the patch shipped, so patching does not undo a prior break-in. Run a full scan for webshells, modified framework and core files, PHP inside upload directories and exposed keys, and review web server access and error logs around the exposure window for anomalies.

Scan for signs of compromise
phpshield scan --since 2026-10-20
phpshield scan /var/www --skip runtime --format md -o compromise-report.md

Sources:php.net/archive

Undo interim mitigations you no longer need

Once every worker runs the patched build, restore memory_limit, post_max_size and max_input_nesting_level to normal values so the host is not left fragile. Keep session.upload_progress off and (on 8.5) fatal_error_backtraces off if you do not use them.

Sources:php.net/archive

Confirm the fixed version is live on every SAPI

Check the CLI, every FPM pool and the web-facing runtime. The version a browser sees can differ from the CLI. Confirm each reports the branch's fixed release or newer.

Confirm the fixed version is live on every SAPI
php -v
php8.4 -v
curl -sI https://your-site.example/ | grep -i x-powered-by

Sources:php.net/archive

Prefer the command line?

The CLI runs the same playbook on your host and can auto-detect your platform: phpshield fix.