Guide · Advanced · Operations teams who cannot patch immediately

PHP2Shell mitigations and their limits

The official interim PHP settings, why memory_limit=-1 is not safe, and how to verify controls before request parsing.

Published22 October 2026 Last verified22 October 2026, 16:45 UTC Reading time5 min StatusPublished

Read this first

PHP’s advisory is explicit: updating is the only complete mitigation. The settings below reduce exposure to specific attack paths. They do not make an installation safe. If you can update to 8.2.35, 8.3.36, 8.4.27 or 8.5.12, do that instead of building a permanent architecture around interim controls.

The controls are also not interchangeable between the two CVEs. Apply the set that matches the attack surface you are reducing, and keep the update as the exit condition.

Controls for CVE-2026-109618 (OOM corruption)

memory_limit = -1

This looks backwards. Raising the memory limit sounds like increasing the blast radius. The reason is specific to this flaw: the vulnerable path is reached when an allocation fails while request data is partly initialised. Removing the PHP-level memory limit removes that particular controllable failure. PHP still warns that an underlying allocation failure can reach the vulnerable path, so this is not a complete fix.

Apply it in a place applications cannot override:

; php.ini or an FPM pool
php_admin_value[memory_limit] = -1

In Apache configuration, the equivalent is a php_admin_value directive in the relevant context. For FPM, a pool-level php_admin_value cannot be changed with ini_set(). Check that no application, .user.ini, virtual-host or container entrypoint sets a finite limit back. A pool that still reports memory_limit = 128M has not applied the control.

Use a container or cgroup memory limit as the availability boundary. PHP explicitly warns not to substitute RLIMIT_AS or RLIMIT_DATA, because those can cause the allocation failure that reaches the vulnerable path.

post_max_size and request-shape limits

PHP suggests post_max_size = 8M where uploads are not required, and 32M where they are. Keep upload_max_filesize below post_max_size. The values are suggestions, not a universal standard; choose limits that fit the application and document why.

The more important part is the proxy boundary. Enforce limits on decoded request size, input field count, multipart part count, header count and nesting depth at the web server, reverse proxy or load balancer. Make sure the limits also cover requests without a usable Content-Length, because a chunked request can otherwise bypass a simple size check.

Blocking multipart requests entirely where the application does not use them is a stronger control. It is also more likely to break a legitimate feature, so test it against a realistic request set before enabling it broadly.

Controls for CVE-2026-109619 (stale global state)

session.upload_progress.enabled = 0

This removes the confirmed session upload-progress path. It must be set before request parsing. PHP says not to rely on ini_set() or FPM .user.ini. Put it in php.ini, an FPM pool or the server configuration that applies at startup:

session.upload_progress.enabled = 0

Restart every worker afterwards. A configuration file change that is not loaded by the running pool has no effect. If the application relies on upload progress, disabling it changes behaviour; test the user-facing flow and communicate the trade-off.

fatal_error_backtraces = 0 on PHP 8.5

The confirmed fatal-error-backtrace path applies to PHP 8.5. Set:

fatal_error_backtraces = 0

This blocks that confirmed path only. It does not remove other variants. Restart all workers after the change.

pm.max_requests = 1

Bitdefender suggests pm.max_requests = 1 on PHP-FPM to break the same-worker condition that CVE-2026-109619 requires. This is a vendor suggestion, not a complete fix, and it has real costs: every request pays worker startup and teardown, and throughput drops. Use it only as a short bridge while patching, and measure the availability impact before applying it to a busy pool.

Remove attacker-reachable fatal errors and timeouts

The advisory also says to fix attacker-reachable fatal errors and execution timeouts, including failures in callbacks, upload or import processing, and cleanup. Hiding diagnostics with error_reporting, display_errors or @ does not close the path. The diagnostic has to not occur, or the underlying condition has to be fixed.

WAF rules are time, not remediation

Cloudflare deployed managed rules about nine hours before disclosure and reported attack traffic. Its own guidance is that customers should patch even with WAF protection. A WAF can block known patterns and raise the cost of scanning. It cannot guarantee that every variant is matched, and a request that reaches PHP is already past the WAF.

If you rely on a WAF during the transition, confirm the rules are in blocking mode, not monitor mode. Keep them enabled after patching until you have verified the runtime update, then review whether the temporary rules should remain.

Verify the control, not the intention

Configuration drift is common. Check the effective values from the running service:

# FPM: show the effective pool configuration
php-fpm8.3 -tt 2>/dev/null | grep -E 'memory_limit|post_max_size|session.upload_progress|fatal_error_backtraces'

# For a web-SAPI check, use only a localhost or access-restricted diagnostic page and delete it immediately

Confirm which pool serves the site and whether a per-directory or per-application override is in place. For containers, check the environment inside the running container, not only the compose file. For a control panel, check the panel’s PHP configuration for the specific domain.

Record the effective values, the time, and the host. An unverified control is a guess.

Compromise assessment boundaries

A clean error log does not clear a host. Crashes and memory errors are investigation signals, not proof of an attempt or a compromise. Some useful indicators from the vendor reporting:

  • unusually large multipart requests followed by memory-exhaustion errors;
  • repeated PHP worker crashes or Apache AH00052 segmentation faults;
  • unexpected or malformed PHPSESSID response cookies;
  • new files in upload, cache or temporary directories;
  • processes whose parent is a web worker;
  • a change in worker behaviour that does not match a deployment.

Bitdefender suggests a lookback starting 17 October 2026, the date of the earliest reported attempt. If you find a credible compromise indicator, rotate credentials and keys reachable by the PHP process, not only the application login passwords. Treat the web shell or process as evidence and preserve it before rebuilding.

Exit criteria

You are done with interim controls when:

  1. every serving runtime reports a fixed PHP release or a verified vendor backport;
  2. every worker has been restarted or replaced;
  3. the temporary controls have been reviewed and either retained deliberately or removed;
  4. the verification record is stored with the service inventory.

Until then, the controls are a bridge. They are not the destination.

Sources for this page

Revision history

Time (UTC) Recorded change
22 October 2026, 16:45 UTC Initial guide. Configuration examples are illustrative; verify directives and pool names against your PHP version and platform.
22 October 2026, 16:45 UTC Current published version.

Commands and configuration examples are illustrative. Confirm package names, service names and directive scope against your own platform and PHP version. This guide is not a substitute for the vendor advisory.

Search PHP2Shell Live

Type to search the local index Results stay in your browser
↑ ↓ move Enter open Esc close