# PHP CVE-2026-109618 and CVE-2026-109619: early mitigation steps

> Source: <https://ma.ttias.be/php-cve-2026-109618-early-mitigation/>
> Published: 2026-10-21 00:00:00+00:00

I wrote this post on the morning of 21 October, before PHP released the fix. **The fix is out now: PHP 8.5.12, 8.4.27, 8.3.36 and 8.2.35.** Installing it is the only complete fix. [How to apply the patches](https://ma.ttias.be/php-cve-2026-109618-patch/)
has the steps for each distribution.

This page is for servers you cannot patch yet, for example an old PHP without a backported fix. The steps below lower the risk until you can patch. They do not remove it.

Most of this post was written with Claude and Codex, and I tested every config in it. Point your LLM at this page, and let it propose the changes for your own servers.

## What the bugs are[#](#what-the-bugs-are)

- **CVE-2026-109618:** PHP can run out of memory while it sets up a request. The cleanup afterwards then uses data that was never fully set up. The confirmed ways to make PHP run out of memory are in multipart request parsing.
- **CVE-2026-109619:** state from one request can remain after its memory is freed. A later request on the same PHP worker can then use it. The confirmed cases are the session upload-progress feature, and fatal error backtraces on PHP 8.5.

Both can lead to remote code execution without a login. The researchers also showed that an attacker can read parts of the PHP worker’s memory. That memory can hold passwords and API keys ([php2shell.com](https://www.php2shell.com/)
). Every PHP that serves web requests is affected: PHP-FPM, Apache with `mod_php`, `php-cgi`, FrankenPHP and LiteSpeed. PHP rates `mod_php` as the highest risk.

**Attacks have started.** watchTowr’s honeypots saw exploitation on 21 October ([watchTowr](https://watchtowr.com/intelligence/php-cve-2026-109618-faq/)
). Cloudflare saw attack traffic against its customers and attackers changing their requests to get past its rules ([Cloudflare](https://blog.cloudflare.com/php-vulnerabilities-waf-protection/)
). Amazon saw a first attempt on its honeypots on 17 October, four days before PHP published the fix. It now calls the bug “broadly exploited in the wild”, but does not know whether the early attempts would have worked ([AWS](https://aws.amazon.com/blogs/security/amazon-threat-intelligence-identifies-php2shell-exploitation-prior-to-public-disclosure-in-madpot/)
). None of them has named a confirmed break-in. The PHP security team says it inspected the leaked proofs of concept and found no working exploit among them at the time of release ([PHP Foundation](https://thephp.foundation/blog/2026/10/21/todays-php-security-release-and-how-were-strengthening-php-security/)
). On 22 October Sansec [wrote](https://sansec.io/research/php2shell)
that mass exploitation is now less likely than it expected on release day. It says an attack needs specific knowledge of the target server and specific conditions on it. According to Sansec, a researcher found that the public exploits do not work against a default PHP installation. Most need the application to have a separate file inclusion bug. Sansec still says to update PHP.

## What actually helps[#](#what-actually-helps)

No setting fully prevents either bug. PHP says so in both advisories. These measures block the paths that PHP has confirmed. Do them in this order:

| Measure | Bug | What it blocks | Cost | 
|---|---|---|---|
| [Block `multipart` at the web server](#block-multipart-requests-at-the-web-server) | Both | The out-of-memory paths in multipart parsing, and the upload-progress path. On `mod_php` , normal form posts can also trigger CVE-2026-109618, so use`memory_limit = -1` there too | File uploads break, except on paths you allow | 
| [`session.upload_progress.enabled = 0`](#turn-off-session-upload-progress) | CVE-2026-109619 | The confirmed upload-progress path, on `mod_php` and PHP-FPM | Upload progress bars stop working | 
| [`fatal_error_backtraces = 0`](#php-85-turn-off-fatal-error-backtraces) (PHP 8.5 only) | CVE-2026-109619 | The path behind PHP’s strongest confirmed exploit. It is on by default | Fatal errors no longer include a backtrace | 
| [No PHP settings applied per request](#remove-php-settings-applied-per-request) | CVE-2026-109619 | A warning from such a setting is one of the two confirmed triggers | Move the settings to `php.ini` or the FPM pool | 
| [`pm.max_requests = 1`](#php-fpm-one-request-per-worker) (PHP-FPM) | CVE-2026-109619 | State that one request leaves behind for the next request on the same worker | Slower | 
| [`memory_limit = -1`](#remove-the-php-memory-limit) | CVE-2026-109618 | The memory limit that attackers make PHP hit, for multipart and normal form posts. The main fix for `mod_php` . Not complete | One request can use all of the server’s memory | 
| [Low `post_max_size` and input limits](#lower-post_max_size-and-input-limits) | CVE-2026-109618 | Makes it harder to push PHP out of memory | Large forms and uploads may fail | 
| [Fix fatal errors attackers can trigger](#fix-fatal-errors-that-attackers-can-trigger) | CVE-2026-109619 | The confirmed PHP-FPM path, which starts with a fatal error in your application | Developer time | 

None of these covers a fatal error or out-of-memory condition that an attacker can trigger inside your own application. Wiz, one of the reporters, [describes](https://www.wiz.io/blog/php2shell-php-rce)
an OPcache path. These measures are not confirmed to cover it.

Set every PHP setting in `php.ini`, the FPM pool or the web server config. Then restart every PHP worker. A reload is not enough. Do not use `ini_set()` or `.user.ini` for any of them ([PHP’s advisory](https://www.php.net/archive/2026.php)
).

If you cannot do any of this, take the site offline until you can patch. [Read-only mode](#make-the-site-read-only)
blocks most, but not all, confirmed paths.

## Block multipart requests at the web server[#](#block-multipart-requests-at-the-web-server)

This stops the request before PHP sees it. PHP itself recommends it for apps that do not use multipart ([advisory](https://github.com/php/php-src/security/advisories/GHSA-w8hw-w9fx-j79w)
).

**On `mod_php`, this is not enough.** Calif, one of the reporters, also triggered CVE-2026-109618 with a normal form post (`application/x-www-form-urlencoded`) on Apache with `mod_php`. PHP-FPM is not affected by that path, because it stops the worker when request startup fails ([Calif](https://calif.io/php2shell/)
). On `mod_php`, also [remove the PHP memory limit](#remove-the-php-memory-limit)
.

Tested versions: nginx 1.29.8, Apache 2.4.69, lighttpd 1.4.85 and Caddy 2.10.2, on 21 October 2026. Each rule returned `403` for multipart requests, with POST and PUT, in lowercase and uppercase. GET, JSON and `application/x-www-form-urlencoded` requests still returned `200`. PHP also parses `Content-Type: MULTIPART/FORM-DATA` in uppercase. So every rule ignores case.

### nginx: block all multipart[#](#nginx-block-all-multipart)

```
# http {} context, e.g. /etc/nginx/conf.d/block-multipart.conf
map $http_content_type $block_multipart {
    default       0;
    "~*multipart" 1;
}

# inside every server {} that passes requests to PHP, including default/catch-all servers
if ($block_multipart) {
    return 403;
}
```

If an `error_page 403` sends the request back to PHP, use `return 444;` instead.

### nginx: allow multipart only on your upload endpoints[#](#nginx-allow-multipart-only-on-your-upload-endpoints)

```
map "$http_content_type|$uri" $block_multipart {
    default 0;
    "~*multipart.*\|/(avatar/upload|livewire/upload-file)$" 0;
    "~*multipart" 1;
}
```

Replace the paths with your own upload endpoints. Tested results: `/login` returned 403. `/avatar/upload` and `/livewire/upload-file` returned 200. `/avatar/upload?x=1` also returned 200. `$uri` does not include the query string. `/x/avatar/upload/y` returned 403. The allowed endpoints stay exposed until you patch.

### Apache 2.4[#](#apache-24)

```
<If "req('Content-Type') =~ /multipart/i">
    Require all denied
</If>
```

Put this in every vhost that serves PHP. Use `req('Content-Type')`, not `%{CONTENT_TYPE}`. Tested with `httpd -t` and with requests.

The same block works in `.htaccess`, for shared hosting. `AllowOverride` has to permit it. Tested: a GET returned 200, and a multipart POST and an uppercase `MULTIPART` PUT both returned 403.

### Caddy[#](#caddy)

```
example.com {
    @multipart header_regexp Content-Type (?i)multipart
    respond @multipart 403

    # ... php_fastcgi / php_server as before
}
```

Tested with `caddy validate` and with requests.

### lighttpd[#](#lighttpd)

```
server.modules += ( "mod_access" )

$REQUEST_HEADER["Content-Type"] =~ "(?i)multipart" {
    url.access-deny = ( "" )
}
```

Tested with `lighttpd -tt` and with requests.

### At your CDN or WAF[#](#at-your-cdn-or-waf)

A block at the CDN only helps if your server accepts traffic from the CDN alone. Otherwise attackers send the request straight to your server’s IP address.

- **Cloudflare** has managed rules named “Generic Rules - Anomalous Execution” in the Managed and Free rulesets ([WAF changelog](https://developers.cloudflare.com/changelog/product/waf/) ). Its CTO says they cover “the known exploit vectors” ([source](https://x.com/dok2001/status/2112811965735202970) ). On the Free plan they are on automatically. On Pro, Business and Enterprise, check that the Managed Ruleset is deployed in block mode ([Cloudflare](https://blog.cloudflare.com/php-vulnerabilities-waf-protection/) ). To block all multipart requests at Cloudflare as well, add a custom rule with action**Block** (untested):

```
any(lower(http.request.headers["content-type"][*])[*] contains "multipart")
```

- **Bunny Shield** blocks the public exploit’s requests with a managed rule, according to[bunny.net](https://bunny.net/blog/cve-2026-109618-and-cve-2026-109619/) .
- **AWS WAF** has the rule`PHP2Shell_BODY` for CVE-2026-109619 in version 2.3 of its PHP application managed rule group, now the default version. AWS says a WAF only partly helps against CVE-2026-109618 ([AWS](https://aws.amazon.com/blogs/security/using-aws-waf-to-apply-mitigations-for-php2shell-rce-vulnerabilities-cve-2026-109618-and-cve-2026-109619/) ).
- **Imunify360** ModSecurity ruleset 8.83 blocks the public exploit, according to[CloudLinux](https://blog.cloudlinux.com/php-cve-2026-109618-and-cve-2026-109619) . It needs the Imunify360 WAF to be on.

The vendor rules match known exploit requests. A changed request may get through. The custom multipart rule blocks every multipart request, changed or not.

## Turn off session upload progress[#](#turn-off-session-upload-progress)

PHP says this “blocks the confirmed session paths but not other variants” ([advisory](https://github.com/php/php-src/security/advisories/GHSA-6wmf-4rv5-p3rp)
). PHP also suggests leaving it off after you patch, if you do not use the feature. I tested each config below. With the setting off, a multipart request with a `PHP_SESSION_UPLOAD_PROGRESS` field no longer created a session file.

In `php.ini`, as a drop-in file in each SAPI’s scan directory, for example `/etc/php/8.4/fpm/conf.d/99-disable-upload-progress.ini`:

```
session.upload_progress.enabled = 0
```

Or in the PHP-FPM pool, where applications cannot change it:

```
; /etc/php/8.4/fpm/pool.d/www.conf
php_admin_flag[session.upload_progress.enabled] = off
```

FrankenPHP often loads no `php.ini`. Set it in the global options block of the Caddyfile. With Laravel Octane, use the file you pass with `--caddyfile`:

```
{
    frankenphp {
        php_ini session.upload_progress.enabled 0
    }
}
```

Repeat this for every PHP version that serves requests. With `mod_php`, add the `php.ini` file to `apache2/conf.d` too.

## PHP 8.5: turn off fatal error backtraces[#](#php-85-turn-off-fatal-error-backtraces)

`fatal_error_backtraces` is on by default in PHP 8.5. Applications can change it. Set it in the FPM pool:

```
php_admin_flag[fatal_error_backtraces] = off
```

With `mod_php`, use `php_admin_flag fatal_error_backtraces off` in the Apache config. PHP says this removes the path behind its confirmed remote code execution on `mod_php`. PHP also says it does not cover the other paths ([advisory](https://github.com/php/php-src/security/advisories/GHSA-6wmf-4rv5-p3rp)
).

## Remove PHP settings applied per request[#](#remove-php-settings-applied-per-request)

Some PHP settings are applied per request. A warning or deprecation notice from one of them can trigger CVE-2026-109619. That covers `php_value` and `php_flag` in `.htaccess` or the Apache config with `mod_php`. It also covers `fastcgi_param PHP_VALUE` or `PHP_ADMIN_VALUE` sent by nginx to PHP-FPM. Move such settings into `php.ini` or the FPM pool. Remove settings that are invalid or deprecated. Settings in `php.ini` and the FPM pool are applied at startup. So they do not trigger this ([advisory](https://github.com/php/php-src/security/advisories/GHSA-6wmf-4rv5-p3rp)
).

## PHP-FPM: one request per worker[#](#php-fpm-one-request-per-worker)

```
pm.max_requests = 1
```

Each worker then exits after one request. So it cannot carry state into the next request. It does not cover paths that need only one request, or state that workers share ([php2shell.com](https://www.php2shell.com/)
). It costs speed. Every request starts a new worker. PHP lists it “if performance is not a factor”.

PHP-FPM already stops a worker when request startup fails. That blocks the version of CVE-2026-109618 that PHP confirmed on `mod_php`. In that version, the attack runs before your code. PHP says FPM is still affected in other ways.

## Remove the PHP memory limit[#](#remove-the-php-memory-limit)

```
php_admin_value[memory_limit] = -1
```

PHP calls this the main setting for CVE-2026-109618 if you cannot update. Calif, one of the reporters, recommends it for both PHP-FPM and `mod_php`, together with turning off upload progress on PHP-FPM ([Calif](https://calif.io/php2shell/)
). It takes away the limit that attackers make PHP hit. It is not complete. PHP can still run out of memory in other ways. Without a limit, one request can use all of the server’s memory. So put PHP in a container or cgroup with a memory limit. Do not use `RLIMIT_AS` or `RLIMIT_DATA` for that. Those trigger the bug ([advisory](https://github.com/php/php-src/security/advisories/GHSA-w8hw-w9fx-j79w)
). Check that no pool, directory or app sets a limit again. Set a real limit again after you patch.

## Lower post_max_size and input limits[#](#lower-post_max_size-and-input-limits)

PHP suggests `post_max_size = 8M` for apps without uploads, and `32M` for apps with uploads. Keep `upload_max_filesize` below it. It also suggests lowering `max_input_nesting_level` (default 64) and limiting `max_input_vars` (default 1000), `max_multipart_body_parts` and `max_file_uploads` (default 20). It gives no values for those. Use the lowest your app still works with. These make it harder to push PHP out of memory. They do not make PHP safe.

## Fix fatal errors that attackers can trigger[#](#fix-fatal-errors-that-attackers-can-trigger)

On PHP-FPM, the attack that PHP confirmed starts inside your application: a request causes a fatal error at a moment when PHP leaves state behind. PHP recommends removing fatal errors and `Maximum execution time exceeded` timeouts that an attacker can trigger. Look in upload, import, error page and plugin code, not only in normal page requests ([advisory](https://github.com/php/php-src/security/advisories/GHSA-6wmf-4rv5-p3rp)
). Check your PHP error log for fatal errors and timeouts on routes that anyone can reach. A clean log does not prove you are safe.

## Make the site read-only[#](#make-the-site-read-only)

If you cannot apply the steps above, reject every request method except `GET` and `HEAD`. Most confirmed paths need a request body, so this blocks them. It does not block the fatal error backtrace path on PHP 8.5, which needs no request body. Turn off [`fatal_error_backtraces`](#php-85-turn-off-fatal-error-backtraces)
as well. Visitors can still read the site. Logins, forms and checkouts stop working. In nginx:

```
if ($request_method !~ ^(GET|HEAD)$) {
    return 503;
}
```

Tested in nginx 1.29: a `GET` returned 200, and a `POST` and a multipart `PUT` both returned 503.

To take a site fully offline, stop the web server or make it answer `503` for every request. Do not only disable the PHP handler. Apache and nginx then serve your `.php` files as plain text, passwords included. I tested this with Apache 2.4 and nginx 1.29. Stopping only PHP-FPM was safe in my test. nginx answered `502`.

Plenty of people did take their site offline. At the peak, [18.8% of the sites Oh Dear monitors were down](https://ma.ttias.be/php-land-last-24-hours/)
, against 0.9% on a normal day.

## Limit the damage[#](#limit-the-damage)

AppArmor or SELinux can stop the PHP worker from starting other programs, such as a shell ([php2shell.com](https://www.php2shell.com/)
). That does not stop the bug. It does not stop the memory leak either. Code inside the PHP process can still read your files and database credentials.

## Check that the mitigation works[#](#check-that-the-mitigation-works)

``` bash
# Effective value for PHP-FPM (should say Off)
$ php-fpm8.4 -i | grep -E 'session.upload_progress.enabled|fatal_error_backtraces'
session.upload_progress.enabled => Off => Off

# Web server block, from outside (should print 403)
$ curl -s -o /dev/null -w '%{http_code}\n' -F a=b https://example.com/
403
```

From inside the app, `ini_get('session.upload_progress.enabled')` should return `"0"`.

## Watch for signs of an attack[#](#watch-for-signs-of-an-attack)

The researchers [list](https://www.php2shell.com/)
these signs. None of them proves an attack. Not seeing them does not prove you are safe either:

- Unusual multipart requests, followed by memory-limit errors or repeated HTTP 500 responses.
- `PHPSESSID` cookies in responses that are unexpectedly large or malformed.
- PHP workers that crash or restart without a clear reason.

Amazon adds one more sign: POST requests with `PHP_SESSION_UPLOAD_PROGRESS` in the form data. It says to search your logs from 17 October, not from 21 October. It also lists six IP addresses it saw attacking, but they belong to shared anonymisation services, so look into hits rather than blocking them ([AWS](https://aws.amazon.com/blogs/security/amazon-threat-intelligence-identifies-php2shell-exploitation-prior-to-public-disclosure-in-madpot/)
).

Also alert when a PHP worker (`php-fpm`, `php-cgi`, `httpd` or `apache2`) starts a shell or a download tool ([Hadrian](https://hadrian.io/vulnerability-alerts/php2shell-leaked-critical-php-rce-cve-2026-109618-cve-2026-109619)
). Some apps start other programs on purpose, for example to resize images. Check what yours normally runs. Treat a hit as a break-in. Isolate the server, keep the logs, and look for web shells, new cron jobs and new user accounts.

A memory leak leaves no web shell or new process behind. If you think a server was attacked, change the passwords, tokens and keys it holds. Patching does not remove anything an attacker left behind.

## After you patch[#](#after-you-patch)

Install the fix and restart every process that runs PHP: PHP-FPM, Apache with `mod_php`, Octane and queue workers. The steps are in [how to apply the patches](https://ma.ttias.be/php-cve-2026-109618-patch/)
. You can leave upload progress off. Put a real `memory_limit` back. The patch adds a [hard memory limit above it](https://ma.ttias.be/php-memory-hard-limit/)
, so check what that means for your workers. For the other changes, see [roll back the early mitigations](https://ma.ttias.be/php-cve-2026-109618-patch/#roll-back-the-early-mitigations)
.

## Suggestions that did not help[#](#suggestions-that-did-not-help)

Before the release, nobody outside the PHP security team knew the details. These suggestions, here and elsewhere, turned out to give a false sense of security:

- **Hiding errors** with`error_reporting` ,`display_errors = 0` or`@` . PHP reads the error state before it decides whether to show or log the warning. So this does not protect you ([advisory](https://github.com/php/php-src/security/advisories/GHSA-6wmf-4rv5-p3rp) ).
- **Setting a mitigation with `ini_set()` or `.user.ini`.** These apply too late, after PHP has already parsed the request. PHP says not to rely on them.
- **`file_uploads = Off`.** PHP still parses the multipart body. It only skips the file parts.
- **Checks in your application:** middleware, CSRF checks, logins, maintenance mode and security plugins. The startup attacks run while PHP sets up the request, before any of your code runs. Fixing fatal errors in your code does help, see[fix fatal errors that attackers can trigger](#fix-fatal-errors-that-attackers-can-trigger) .
- **`RLIMIT_AS` or `RLIMIT_DATA` as a memory limit.** These trigger the bug instead of stopping it.
- **`session.use_strict_mode = 1`.** It is good practice. But PHP’s advisories do not list it, and no source shows it stops either bug.
- **A WAF or CDN while your server is reachable directly.** Attackers skip it by sending the request to your server’s IP address.
- **Disabling only the PHP handler to take a site offline.** It makes the web server show your PHP source code, passwords included.
