PHP2Shell mitigations when you cannot patch yet

Updated 5 min read php2shell.eu

Every source on PHP2Shell says the same thing first: the only complete fix is to update PHP. This guide is for the hours or days in between, when a server cannot be updated yet and must not simply be left as it is. It sets out the measures the PHP project published, what each one actually covers, and how to apply them so they take effect.

Start with the network, not php.ini

The most effective stop-gap is not a PHP setting. It is making the server unreachable to the people who would attack it. CERT.LV's guidance is explicit: systems that cannot be updated to a safe PHP version must have their access from the internet restricted, and should not be publicly reachable until the risk is removed. Latvia applied that principle to its own state services: on 21 October access to affected systems was limited to Latvian IP addresses while patches were rolled out, and the shared state and municipal website platform was switched off for an evening until it had been updated.

  • Put administrative and internal PHP applications behind a VPN or an IP allow-list.
  • Put public sites that cannot be closed behind a WAF or CDN that has published PHP2Shell rules, and make sure the origin accepts traffic only from that service. A rule in front does nothing if the origin's own IP address answers requests.
  • Where a site is not business-critical, take it offline until it is patched. A maintenance page costs less than a rebuilt server.

How to apply PHP settings so they actually work

Several of the attack paths run while PHP is still parsing the request, before your application starts. A setting applied too late is no setting at all. The PHP advisory is specific about where the session setting must go: in php.ini, an FPM pool, or server configuration that applies before request parsing. Settings an application applies with ini_set(), and PHP-FPM .user.ini files, take effect too late.

  • For PHP-FPM, set values in the pool file with php_admin_value or php_admin_flag, which applications cannot override.
  • For Apache mod_php, use php_admin_value or php_admin_flag in the server or virtual host configuration.
  • Restart every worker afterwards. A running FPM child or Apache process keeps its old configuration.
  • Check the effective value with a phpinfo() page served through the web server, then delete the page.
; PHP-FPM pool, e.g. /etc/php/8.3/fpm/pool.d/www.conf
php_admin_flag[session.upload_progress.enabled] = off
php_admin_value[post_max_size] = 8M
php_admin_value[upload_max_filesize] = 6M

# Apache with mod_php, server or virtual host configuration
php_admin_flag session.upload_progress.enabled off
php_admin_value post_max_size 8M

Against CVE-2026-109619: stale global state

Measures for CVE-2026-109619
MeasureWhat it blocksCost
session.upload_progress.enabled=0The confirmed session upload progress paths, which work against both mod_php and PHP-FPM and do not need the application to call session_start()Upload progress bars stop working. Few applications use them
fatal_error_backtraces=0 (PHP 8.5 only)The confirmed fatal error backtrace path, the basis of the demonstrated mod_php exploit. The setting is on by default in 8.5Fatal errors are logged without a backtrace
Remove invalid or deprecated per-request settingsEarly warnings that consume stale state before the next request is initialised, for example legacy assert.* settings on PHP 8.3 and later or legacy session settings on PHP 8.4 and later, passed per directory or by the FastCGI front endConfiguration clean-up
Fix fatal errors and timeouts an attacker can reachWays of ending a request abruptly, which is what leaves stale state behindApplication work, not a switch
The PHP advisory adds that pm.max_requests=1 recycles each FPM worker after one request, which reduces but does not remove the chance that two requests meet in the same worker, at a real performance cost. It also suggests keeping session.upload_progress.enabled=0 after patching if you do not need the feature.

Against CVE-2026-109618: out-of-memory corruption

This flaw needs PHP to run out of memory at the wrong moment. The measures either remove PHP's own memory-limit failure, or make it harder for one request to consume that much memory.

Measures for CVE-2026-109618
MeasureWhat it doesCost
memory_limit=-1, set with php_admin_valueRemoves the controllable PHP memory-limit failure. Check that no pool, directory or application restores a finite limitA runaway request can now exhaust the machine. An underlying allocation failure can still reach the vulnerable path
A cgroup or container memory limitContains each worker once PHP's own limit is gone, preferably with a policy that kills only the affected worker. The PHP advisory notes it does not guarantee the allocation never fails, and killing a whole container also kills the master and every workerRequests on a killed worker fail; in threaded setups other requests on that process fail too
Low post_max_size8M where the application needs no uploads, 32M where it does, with upload_max_filesize below itLarger legitimate uploads are rejected
Limits at the web server or proxyCaps on decoded request size, input field count, multipart part count, header count and nesting depth, including for requests without a usable Content-LengthNeeds testing against complex forms
Lower max_input_nesting_levelReduces the work deeply nested field names createComplex forms may need a higher value
Pairing memory_limit=-1 with a cgroup v2 memory.max limit is how the researchers and the PHP project frame it: PHP stops enforcing the limit itself and the operating system contains the damage.

What these measures leave open

The PHP advisory for CVE-2026-109619 says there is no configuration-based mitigation that covers every operation, because an application, extension or server path can leave stale state behind or use it without a predictable log entry. The fixes in the released versions address the underlying failure pattern; the settings above address the paths that have been demonstrated. Treat a mitigated server as still vulnerable, with fewer known ways in.

After you patch

Do not simply revert everything. The PHP advisory for CVE-2026-109618 still recommends a conservative post_max_size, limits on decoded request size at the front end, and bounds on input variables, multipart parts and nesting depth, because they reduce the risk of the new hard memory limit being hit and of denial of service. Revisit any temporary memory_limit=-1 once the fixed release is running: the fix introduces a hard memory limit above the normal one that kills a worker outright, without shutdown functions or destructors.

Sources

  1. Security advisory: Critical remote code execution vulnerabilities, update now PHP project, php.net · 2026 The full list of measures for each CVE and where each must be applied.
  2. GHSA-w8hw-w9fx-j79w: RCE via OOM corruption PHP project, GitHub · 2026 The memory-limit measures, the warning about RLIMIT_AS and RLIMIT_DATA, and the hardening to keep after patching.
  3. GHSA-6wmf-4rv5-p3rp: RCE via stale global state PHP project, GitHub · 2026 The session and backtrace measures, why hiding errors does not help, and the absence of a complete configuration mitigation.
  4. Saistībā ar globāli atklātām PHP ievainojamībām Latvijā ievieš preventīvus pasākumus CERT.LV · 2026 Restricting internet access to systems that cannot be updated.

Questions

Related questions

Is setting session.upload_progress.enabled=0 enough?
No. It blocks the confirmed session paths for CVE-2026-109619, but not the other variants, and it is not among the measures the PHP project lists for CVE-2026-109618. It is one of several measures, and all of them together still do not make an installation safe.
Can I set these values in .htaccess or with ini_set()?
Not with ini_set() or an FPM .user.ini file: the PHP advisory says both apply too late. On Apache mod_php, per-directory php_flag settings, including .htaccess, are applied before request startup, but php_admin_flag in the server or virtual host configuration is better because applications cannot override it. Restart the workers afterwards.
Will memory_limit=-1 take my server down?
It can, if one request consumes all available memory. That is why the PHP project pairs it with a container or cgroup limit that kills only the affected worker. Test it on a staging server first if you can.