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 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# #
- 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
). 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 ). Cloudflare saw attack traffic against its customers and attackers changing their requests to get past its rules (Cloudflare ). 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 ). 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 ). On 22 October Sansec wrote 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# #
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 |
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 usememory_limit = -1 there too |
File uploads break, except on paths you allow |
session.upload_progress.enabled = 0 |
CVE-2026-109619 | The confirmed upload-progress path, on mod_php and PHP-FPM |
Upload progress bars stop working |
fatal_error_backtraces = 0 (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 | 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) |
CVE-2026-109619 | State that one request leaves behind for the next request on the same worker | Slower |
memory_limit = -1 |
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 |
CVE-2026-109618 | Makes it harder to push PHP out of memory | Large forms and uploads may fail |
| Fix fatal errors 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 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
).
If you cannot do any of this, take the site offline until you can patch. Read-only mode blocks most, but not all, confirmed paths.
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 ).
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
). On mod_php, also 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#
map $http_content_type $block_multipart {
default 0;
"~*multipart" 1;
}
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#
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#
<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#
example.com {
@multipart header_regexp Content-Type (?i)multipart
respond @multipart 403
}
Tested with caddy validate and with requests.
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#
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 ). Its CTO says they cover “the known exploit vectors” (source ). On the Free plan they are on automatically. On Pro, Business and Enterprise, check that the Managed Ruleset is deployed in block mode (Cloudflare ). To block all multipart requests at Cloudflare as well, add a custom rule with actionBlock (untested):
any(lower(http.request.headers["content-type"][*])[*] contains "multipart")
- Bunny Shield blocks the public exploit’s requests with a managed rule, according tobunny.net .
- AWS WAF has the rule
PHP2Shell_BODYfor 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 ). - Imunify360 ModSecurity ruleset 8.83 blocks the public exploit, according toCloudLinux . 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# #
PHP says this “blocks the confirmed session paths but not other variants” (advisory
). 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# #
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
).
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
).
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 ). 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# #
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
). 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
). 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# #
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# #
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
). 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# #
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
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 , against 0.9% on a normal day.
Limit the damage# #
AppArmor or SELinux can stop the PHP worker from starting other programs, such as a shell (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# #
$ php-fpm8.4 -i | grep -E 'session.upload_progress.enabled|fatal_error_backtraces'
session.upload_progress.enabled => Off => Off
$ 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# #
The researchers list 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.
PHPSESSIDcookies 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
).
Also alert when a PHP worker (php-fpm, php-cgi, httpd or apache2) starts a shell or a download tool (Hadrian
). 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# #
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
. You can leave upload progress off. Put a real memory_limit back. The patch adds a hard memory limit above it
, so check what that means for your workers. For the other changes, see roll back the early mitigations
.
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 = 0or@. PHP reads the error state before it decides whether to show or log the warning. So this does not protect you (advisory ). - 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, seefix fatal errors that attackers can trigger .
RLIMIT_ASorRLIMIT_DATAas 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.