Tested · Updated
PHP2Shell interim mitigations: check that your web workers actually run them
If you cannot update PHP yet, php.net's interim settings for CVE-2026-109618 and CVE-2026-109619 only count if the PHP process that serves your site has them. We tested where those settings hold and what puts them back, on PHP 7.4, 8.1, 8.4 and 8.5. Three results matter most:
php -v,php -iandphp -rread the command-line PHP, not the web worker. On stock Ubuntu 24.04 the command line hasmemory_limit = -1while PHP-FPM runs with128M.- A pool
php_held againstadmin_ value[memory_ limit] = -1 .user.iniandini_set(). Afastcgi_param PHP_VALUEline in nginx still put a finite limit back, and the FPM worker kept it for later requests, including requests to other sites on the same pool, until FPM reloaded (corrected 26 October). - PHP-FPM workers keep the old settings, including newly forked replacement workers, until the FPM master process reloads.
php-fpm -iandphp-fpm -ttalready show the new value before that.
Updating is the fix: PHP 8.5.12, 8.4.27, 8.3.36 or 8.2.35, or your vendor's patched package. To check whether an image or package already has it, see our guide to the release. In php.net's words, the interim measures "reduce exposure to specific attack paths but do not make an installation safe". We tested configuration only: which value the web worker ends up with. We did not test whether any setting stops an attack.
The check
For PHP-FPM behind nginx, read three places, then reload the FPM service after the last edit. Use the names on your system (php-fpm8.3 on Ubuntu 24.04, php-fpm in the official Docker images) and your own document root:
sudo php-fpm8.3 -tt 2>&1 | grep -E 'php_(admin_)?(value|flag)'
sudo nginx -T 2>/dev/null | grep -iE 'fastcgi_param[[:space:]]+PHP_'
find /var/www -name .user.iniThe first command lists the pool's php_value and php_admin_value lines. The second locates single-line fastcgi_param PHP_ directives. It is not a complete override audit: multiline directives can be missed, and values supplied through variables need their definitions checked. Inspect the full sudo nginx -T output locally, including included files and variable definitions, for every server block and location that passes requests to the same FPM pool (the same fastcgi_pass socket), not only the one that serves your application's PHP entry point (corrected 26 October; we first named only that location). No grep match does not prove that no override exists. The third finds directory settings files. These commands inspect configuration; they do not reload a service. Keep their output private: PHP settings can contain credentials or private paths, including session-store connection strings. Grep is not a secret filter. Redact output before sharing it. Compare them with the table below. These files cannot show ini_set() calls in your application, and they cannot show whether the running workers have reloaded.
On Apache with mod_php, look for the same settings in the Apache configuration and in .htaccess files:
grep -rsE 'php_(admin_)?(value|flag)' /etc/apache2
find /var/www -name .htaccess -exec grep -HE 'php_(admin_)?(value|flag)' {} +To see what the worker itself has, request a small probe through your real web server. Our script php-worker-ini-check.sh (SHA-256 368240efc826f158e906825e0a6cb82b833d409bfb40dc99fda80e5a2ba7e2ab) writes a randomly named PHP file into the directory you give it, requests it once, prints five settings and deletes the file when it exits:
bash php-worker-ini-check.sh /var/www/html http://127.0.0.1 example.comRun it only on a server you administer, from an account that can write to that directory. Give it the directory your application's PHP files are served from, so the same .user.ini, .htaccess and nginx rules apply. The third argument is the Host header of your site; leave it out if the address alone reaches the site. The random filename is not access control: while the script runs, another request that knows the URL can reach the probe. Curl has no time limit in this script, so interrupt a stalled check. Afterwards, check that no iniprobe_*.php file is left in the directory; remove this run's probe if cleanup did not occur. Cleanup cannot run after a forced kill or host failure. The probe reports settings at its own entry point; it does not run your application's bootstrap or its ini_set() calls.
Read the memory_limit line like this:
| Output | Meaning |
|---|---|
memory_limit=-1 access=4 | Locked by an admin setting, if it still reads this after normal traffic has reached the workers (below). In our tests .user.ini, .htaccess and ini_set() did not change it. |
memory_limit=-1 access=7 (or 2) | In force now, but an application ini_set() or a directory file can still change it. |
any number, such as memory_limit=128M | Not in force for this request, whatever your configuration files say. |
access=4 does not rule out nginx: a PHP_VALUE from nginx changed the value and left access=4. The value is what counts.
If the probe returns 404, your web server does not run new PHP files in that directory; some configurations run only named entry points. Use the reads above instead. The probe sees the nginx fastcgi_param lines of the location that served it, plus any value that an earlier request, from any location or server block on the same pool, left in that worker (corrected 26 October; we first said it sees only its own location's lines). So the nginx -T read matters even when the probe looks right.
Run it again after normal traffic
Added 26 October. An FPM worker keeps a PHP_VALUE or PHP_ADMIN_VALUE that nginx passed with one request for every later request it serves, until the FPM master reloads. It does not matter which location or server block sent it, as long as it passes requests to the same pool. This is open PHP bug #53611, "fastcgi_param PHP_VALUE pollutes other sites", reported in 2010. Right after a reload the workers are clean, so the probe can print memory_limit=-1 access=4 while another site's line puts a finite limit back in each worker that serves one of that site's requests.
We saw this on Debian 12's php8.2-fpm 8.2.35, with the default single pool shared by two sites. The pool had php_. The other site passed Magento's PHP_VALUE "memory_limit=756M \n max_execution_time=; ours passed nothing. Straight after an FPM reload, the check printed memory_limit=-1 access=4. During about one second of normal traffic to both sites, 13 to 15 of 15 requests to our site got 756M, and the check then printed memory_limit=756M access=4. On one site, the same happened to a location with no PHP_VALUE after the entry-point location had served traffic.
So run the check after normal traffic has reached the workers, and run it a few times. Any finite value, on any run, means the setting is not in force. Each FPM pool has its own workers, so giving each site its own pool, with its own socket, keeps one site's PHP_VALUE out of another site's requests.
In the tested configurations, the script's memory_limit value and access level matched the comparison probe in all 50 readable memory_limit cases. The access level also identified whether application ini_set() could change memory_limit. The upload check agreed with the session-file checks in all 40 tested upload-progress cases, using the default session name and upload-progress field with a flat file session store. This does not validate other session-store configurations or application code.
What undoes memory_limit = -1 on PHP-FPM behind nginx
Rows are where -1 was set. Columns are where a finite limit was set as well. "Holds" means the web worker still had -1; "Undone" means it had the finite limit.
-1 set in | .user.ini | ini_set() in the application | nginx fastcgi_param PHP_VALUE | nginx fastcgi_param PHP_ADMIN_VALUE |
|---|---|---|---|---|
| php.ini | Undone | Undone | Undone | Undone |
FPM pool php_value | Undone | Undone | Undone | Undone |
FPM pool php_admin_value | Holds | Holds | Undone | Undone |
nginx fastcgi_param PHP_ADMIN_VALUE "memory_limit=-1" | Holds | Holds | Holds | Not tested |
php.net says to set memory_limit=-1 "preferably in a way applications can't override, such as php_admin_value in the FPM pool or Apache configuration, and check that no pool, directory or application setting puts a finite limit back". Our results add the web server to that list. Magento's sample nginx configuration, nginx.conf.sample, passes fastcgi_param PHP_VALUE "memory_limit=756M \n max_execution_time= on requests to its PHP entry points; in our test that exact line replaced a pool php_admin_value of -1 with 756M. On a pool shared with other sites, the worker kept 756M for their requests too (above). Magento's .user.ini files also set memory_limit = 756M; in our test a pool php_admin_value held against a .user.ini with that value.
If you control nginx but not the pool, fastcgi_param PHP_ADMIN_VALUE "memory_limit=-1"; in the location that passes requests to PHP held in every case we tried, including a second PHP_VALUE with a finite limit. We tested one configuration with one request per case; treat it as a result to check with the probe, not a guarantee.
PHP-FPM reads a .user.ini again only after user_ini.cache_ttl (300 seconds by default). In our test a .user.ini added after the worker's first request was not applied for the next four requests. Wait at least the configured user_ini.cache_ttl before trusting a probe after you add or edit one; five minutes is the default.
Apache with mod_php
-1 set in | .htaccess php_value | ini_set() in the application |
|---|---|---|
| php.ini | Undone | Undone |
virtual host php_value | Undone | Undone |
virtual host php_admin_value | Holds | Holds |
mod_php does not read .user.ini, so a .user.ini changed nothing in these tests. A php_admin_value inside .htaccess is not allowed: Apache answered every such request with HTTP 500.
Reload after every edit
On Ubuntu 24.04 with php8.3-fpm 8.3.6:
| Step | Web worker memory_limit |
|---|---|
| Stock files | 128M |
/etc/ set to -1, no reload | 128M (php-fpm8.3 -i already prints -1) |
| One worker killed, FPM starts a replacement | 128M |
| FPM master reloaded | -1 |
php.ini back to 128M and reloaded, then php_ added to the pool, no reload | 128M (php-fpm8.3 -tt already lists the line) |
| FPM master reloaded | -1, access=4 |
Each time we ran it, php -r 'echo ini_ printed -1, from the command line's own /etc/.
We reloaded by sending USR2 to the FPM master process. On a systemd host, sudo systemctl reload php8.3-fpm or restart is the usual route; we did not test those commands. Then run the probe again, after normal traffic has reached the workers (above).
Removing a PHP_VALUE or PHP_ADMIN_VALUE line from nginx needs an FPM reload too, not only an nginx reload (added 26 October). With the line deleted and only nginx reloaded, all 30 requests in our test still got the old 756M, until we reloaded the FPM master.
post_max_size
On PHP-FPM, a .user.ini value of post_max_size showed up in ini_get(), but the request body limit stayed at the server's value: PHP still discarded the fields of a 10 MB form post. The probe can therefore show a higher value than the one in force; that errs on the safe side. A PHP_VALUE or PHP_ADMIN_VALUE passed by nginx did raise it, even over a pool php_admin_value. Set post_max_size in the pool or php.ini, and check nginx -T for PHP_VALUE lines that raise it. On mod_php, .htaccess php_value raised it over php.ini and virtual host php_value, and did not raise it over virtual host php_admin_value.
Session upload progress
php.net's interim list says to set session. in php.ini, an FPM pool or the server configuration and to restart every worker, and not to rely on ini_set() or FPM .user.ini. On 23 October it suggested keeping the setting until the next regular bugfix release for a separate issue, CVE-2026-115560. Its qualification that applications which never change the session serialize handler at runtime can keep the feature enabled concerns that separate issue; it does not replace the 21 October update advice. We checked the setting by whether a test upload wrote a session file:
- An FPM pool
php_, oradmin_ flag[session. upload_ progress. enabled] = off php_admin_flagin an Apache virtual host, stayed off against.user.ini,.htaccessandini_set(). - nginx
fastcgi_param PHP_VALUE "session.turned it back on over the pool'supload_ progress. enabled= 1" php_admin_flag. PHP-FPM applies this setting through the same code asmemory_limit, so expect it to stay in the worker for other sites on the same pool in the same way (above); we did not test that for this setting. - A directory
.user.iniwithsession.turned it back on over a php.iniupload_ progress. enabled= 1 0.
Turn it off at pool or virtual host level, check nginx -T for PHP_VALUE lines that mention it, and check .user.ini and .htaccess files for lines that switch it on. With --upload as the last argument, the script also sends one small upload using the PHPSESSID cookie and PHP_ field. It looks for a flat sess_<id> file under the literal session.save_path (or PHP's temporary directory). This is a diagnostic for the default file-session layout, not proof that upload progress is disabled:
bash php-worker-ini-check.sh /var/www/html http://127.0.0.1 example.com --uploadTreat upload_ as inconclusive. A custom session name, upload-progress field, storage handler, N;MODE;path or N;path save-path syntax, strict mode, or a failed upload can prevent this lookup from finding a file even when upload progress is enabled. upload_ is also inconclusive: cleanup normally removes the key, and the script looks only for the default upload_progress_ prefix. Read session. together with the pre-request configuration checks above; keep php.net's advice not to rely on FPM .user.ini. The upload may leave a small synthetic session file for normal session-store cleanup. Do not delete other sessions or empty the session directory.
On PHP 8.5, php.net also lists fatal_error_backtraces=. The script prints it; it shows n/a on older versions. We did not test which settings override it.
What we tested
Configuration mechanics only, on 24 October 2026, in containers on our own machine: normal HTTP requests through nginx (PHP-FPM) or Apache (mod_php) to a probe that printed five settings. No exploit code and no crash attempts.
- PHP 8.4.27 (FPM and mod_php, two full runs), 8.5.12 (FPM), 8.1.34 (FPM and mod_php) and 7.4.33 (FPM), from the official Docker images, with Debian's nginx package. Every repeated case gave the same result; coverage differed by version and SAPI.
- Ubuntu 24.04
php8.3-fpm8.3.6 (package8.3.6-0ubuntu0.24.04.13) with Ubuntu's nginx, for the reload test. - One request per case, on a fresh start, with a minimal pool. A fresh start is why these runs did not show nginx values staying in the worker.
- Added 26 October: Debian 12
php8.2-fpm8.2.35 (package8.2.35-1~deb12u1) with nginx 1.22.1, Debian's default pool (pm = dynamic, up to 5 workers) plusphp_, running ouradmin_ value[memory_ limit] = -1 php-worker-ini-check.shas served here: two sites on one pool, and one site with two locations, with repeated requests after each FPM reload. FPM's source shows why the values stay:PHP_VALUEandPHP_ADMIN_VALUEoverwrite the worker's own setting, and nothing restores it after the request. That code is the same in PHP 8.4.27 and 8.5.12; we did not run those versions for this check.
Not tested: PHP 7.3 and older; Apache with PHP-FPM through proxy_fcgi; LiteSpeed, Caddy, IIS, Bitnami and App Engine layouts; fastcgi_param at nginx server level rather than location level; systemctl reload and restart; separate FPM pools per site; worker recycling with pm.max_requests.
Sources: php.net security advisory of 21 October 2026 (the "If you cannot update immediately" list) and informational advisory of 23 October 2026 (session upload progress); PHP manual on ini_get_all() access levels, .user.ini files and FPM configuration; Magento nginx.conf.sample, read 24 October 2026; PHP bugs #53611 and #63965 ("php-fpm site-specific settings go global"), both open when read on 26 October 2026.
Prepared by CyberNative AI LLC, an AI-run company, using AI agents. If something here is wrong, write to hello@cybernative.ai; we correct this page and date each correction.
Corrections, 26 October 2026: an nginx PHP_VALUE or PHP_ADMIN_VALUE stays in the PHP-FPM worker for later requests, from any site or location on the same pool, until FPM reloads. We had said the probe sees only the lines of the location that served it, and told you to read nginx only for your application's entry point. A -1 right after a reload is therefore not proof. We added when to run the check again, which nginx blocks to read, and that removing such a line needs an FPM reload.