PHP's main stopgap for the first PHP2Shell bug (CVE-2026-109618) is to remove the memory limit by setting memory_limit=-1, somewhere applications can't change it. The usual advice is php_admin_value in the FPM pool.
On a Magento store using the stock nginx config, that doesn't work. The value comes back as 756M, and ini_set() failing makes it look locked in.
The real fix is updating PHP. PHP's words: "There are no complete mitigations, updating is the only safe choice." The fixed releases are 8.5.12, 8.4.27, 8.3.36 and 8.2.35. But if you're waiting on a vendor backport and using this stopgap in the meantime, check it's actually applied.
Where Magento sets memory_limit
Magento 2.4.9 sets it to 756M in five files:
| File | Line | Applies when |
|---|---|---|
.user.ini | 1 | PHP-FPM or CGI, if the document root is the Magento root |
pub/.user.ini | 1 | PHP-FPM or CGI, if the document root is pub |
pub/.htaccess | 45, 69 | Apache with mod_php only (inside IfModule guards) |
.htaccess.sample | 43 | Apache with mod_php, if you use the sample |
nginx.conf.sample | 58, 218 | nginx, passed as fastcgi_param PHP_VALUE |
Line 218 is the one that matters. It sits in the location block for index.php and the other main entry points:
fastcgi_param PHP_VALUE "memory_limit=756M \n max_execution_time=18000"; What we tested
We ran a throwaway PHP-FPM 8.4.13 pool on one worker, bound to localhost, with a one-line script printing ini_get('memory_limit'). A small FastCGI client sent the same parameters nginx would, including Magento's exact PHP_VALUE string. nginx itself wasn't in the loop.
| Pool / php.ini | .user.ini 756M | PHP_VALUE from nginx | Result |
|---|---|---|---|
php.ini -1 | no | no | -1 |
php.ini -1 | yes | no | 756M |
pool php_value -1 | yes | no | 756M |
pool php_admin_value -1 | yes | no | -1 |
pool php_admin_value -1 | no | Magento's | 756M |
pool php_admin_value -1 | yes | Magento's | 756M |
php.ini -1 | yes | memory_limit=2G | 756M |
| none | yes | PHP_VALUE 756M plus PHP_ADMIN_VALUE -1 | -1 |
pool php_admin_value -1 | yes | PHP_VALUE 756M plus PHP_ADMIN_VALUE -1 | -1 |
So the order of who wins looks like this:
-
PHP_ADMIN_VALUEfrom the web server -
.user.ini, unless an admin value has locked the setting -
PHP_VALUEfrom the web server, which also replaces a poolphp_admin_value - Pool
php_valueandphp.ini
Rows 5 and 6 are the surprise. .user.ini can't touch a pool php_admin_value, but PHP_VALUE can.
In those two cases ini_set('memory_limit', '512M') also returned false. The setting is locked, just at the wrong value. If you tested the lock with ini_set() you'd think you were fine.
Why it happens
It's in sapi/fpm/fpm. fpm_main.c reads PHP_VALUE with mode ZEND_INI_USER and then PHP_ADMIN_VALUE with mode ZEND_INI_SYSTEM. Both go through fpm_php_zend_ini_alter_master() in fpm_php.c, which writes the new value without checking whether the setting is already locked. It only tightens the lock when the mode is ZEND_INI_SYSTEM.
So a later PHP_VALUE overwrites the value and leaves the lock in place. The PHP manual promises that php_admin_value can't be overridden by .htaccess or ini_set(). It doesn't say anything about FastCGI parameters, so we wouldn't call this a bug, but it's easy to miss.
We only ran this on 8.4.13, which predates the fix. The same order and the same function are in the current PHP-8.2 to PHP-8.5 branches of php-src, so we'd expect the same result, but please test your own build.
How to fix it
Either option works. Option 1 is row 4 of the table and option 2 is the last two rows.
Option 1: take memory_limit out of PHP_VALUE. Keep the pool setting:
fastcgi_param PHP_VALUE "max_execution_time=18000"; php_admin_value[memory_limit] = -1 Option 2: add PHP_ADMIN_VALUE next to the existing line:
fastcgi_param PHP_VALUE "memory_limit=756M \n max_execution_time=18000"; fastcgi_param PHP_ADMIN_VALUE "memory_limit=-1"; Put it in the same location block, not at server level. nginx only inherits fastcgi_param from the level above when the current level defines none, and this block defines several.
Reload nginx, and restart FPM if you changed the pool.
PHP's other warnings still apply. Memory use will climb, so put a container or cgroup limit around PHP, and don't use RLIMIT_AS or RLIMIT_DATA, because PHP says those can trigger the vulnerable path themselves. Once PHP is updated, take the -1 back out.
How to check what you've really got
php -i on the command line won't help. The CLI has its own ini file and doesn't read .user.ini.
You need to ask through the web server, and Magento's sample config gets in the way: it only runs index, get, static, errors/report, errors/404, errors/503 and health_check, and denies every other .php file. So for a quick check:
- Create
pub/probe-x7f3.php, using your own random name:
<?php header('Content-Type: text/plain'); echo PHP_VERSION, ' ', PHP_SAPI, ' memory_limit=', ini_get('memory_limit'); - Add
probe-x7f3to the regex on the main entry point location and reload nginx. - Request it over HTTPS.
- Delete the file, undo the regex and reload again.
If you changed a .user.ini, allow for user_ini.cache_ttl, which defaults to 300 seconds.
Is anyone attacking Magento with this?
CISA added both CVEs to its Known Exploited Vulnerabilities catalog on 26 October 2026. We haven't seen an authoritative source report attacks aimed specifically at Magento or Adobe Commerce. The attack can land before any application code runs, though, so that isn't a reason to wait.
Sources
- PHP security advisory, 21 October 2026
- GHSA-w8hw-w9fx-j79w, CVE-2026-109618
- Magento 2.4.9 nginx.conf.sample
- PHP manual: FPM configuration
- PHP manual: .user.ini files
- php-src: fpm_php.c
We keep a longer guide covering which PHP version each Magento release needs and where the control panels stand: PHP2Shell and Magento. There's also a Magento and Mage-OS version and patch tracker.
This article was written with AI. The tests were run locally, and the results and sources were checked before publishing.

