In short: A PHP out-of-memory error occurs when a script tries to request more memory than the memory_limit setting allows (default: 128 MB per request). PHP then aborts the request with a fatal error and cleans up. It was precisely this abort and clean-up path that was faulty in CVE-2026-109618, one of the two PHP2Shell vulnerabilities. Since PHP 8.5.12, 8.4.27, 8.3.36 and 8.2.35, memory_limit is a soft limit, supplemented by the hard limit memory_hard_limit_percent.
What does “Allowed memory size exhausted” mean?
The familiar error message reads roughly: “Allowed memory size of 134217728 bytes exhausted (tried to allocate …)”. The first number is the configured limit in bytes, here 128 MB. A script has therefore tried to occupy memory beyond this limit, for example when loading a large file, running a huge database query or through an endless loop.
Important for decision-makers: the limit applies per PHP process and per request, not to the whole server. It protects the server from a single faulty script using up all memory. The total load only emerges from the number of workers running at the same time. With 20 PHP-FPM workers at a 256 MB limit each, that is, arithmetically, over 5 GB.
How PHP counts memory
PHP manages memory for requests through its own memory manager, the Zend Memory Manager. It fetches larger blocks from the operating system and distributes them internally to variables, arrays and objects. PHP checks memory_limit against this reserved value. According to the manual, memory that an extension requests directly from the operating system does not appear in this count.
You can make the values visible in code:
<?php
echo memory_get_usage(), PHP_EOL; // memory currently occupied by the script
echo memory_get_usage(true), PHP_EOL; // reserved by the memory manager; memory_limit is checked against this
$liste = range(1, 200000);
echo memory_get_peak_usage(), PHP_EOL; // peak value so far
unset($liste);
memory_reset_peak_usage(); // reset the peak value, from PHP 8.2
How reference counting and garbage collection release memory again is explained on the page Memory management in PHP.
What happens inside on out-of-memory
In simplified terms, a memory abort proceeds in these steps:
- The script requests memory, for example for a new array element or when reading request data.
- The memory manager checks whether the request exceeds the limit.
- If it does, PHP raises a fatal error and leaves the current processing immediately, even in the middle of a half-finished task.
- Registered shutdown functions still run; many applications use them to log the error.
- At the end, PHP cleans up the request and releases its memory. The worker is then ready for the next request.
The delicate point is step 5. The clean-up has to cope with data structures that were only half set up at the time of the abort. According to the PHP project's security policy, an ordinary memory abort does not count as a security vulnerability as long as the limit rejects the request and only the current request ends. With CVE-2026-109618, exactly this assumption was violated: according to php.net, the out-of-memory condition could interrupt PHP while internal request data was only partly initialised, and the clean-up then treated invalid data as valid. We do not publish details of exploitation; the classification of the vulnerability is found under CVE-2026-109618.
The new soft and hard memory limit
With the security releases of 21 October 2026, the PHP project changed the behaviour. According to the manual, memory_limit has since been a soft limit: a request that exceeds it may in most cases still be completed before the fatal error follows. A separate hard limit restricts how far this may go.
| Directive | Default | Changeable | Meaning |
|---|---|---|---|
memory_limit |
128M | INI_ALL | Memory limit per request, -1 = no limit |
memory_hard_limit_percent |
not set | INI_ALL | Hard limit as a percentage of memory_limit, new since 8.5.12, 8.4.27, 8.3.36, 8.2.35 |
post_max_size |
8M | INI_PERDIR | Maximum size of POST data |
upload_max_filesize |
2M | INI_PERDIR | Maximum size of an uploaded file |
An example from the manual: with memory_limit=128M and memory_hard_limit_percent=150, the hard limit is 192 MB. The manual also names the directive as an identifying feature: if a PHP version does not know it, it is affected by CVE-2026-109618 and CVE-2026-109619. A quick test for every installed PHP version:
php -r 'var_dump(ini_get("memory_hard_limit_percent"));'
# bool(false) means: directive unknown
For packages from distributions with backports, the package changelog remains the authoritative evidence, as described under Checking the PHP version and patch level properly. Also check the PHP-FPM binary separately, because it can have a different version from the command line.
Memory limits and PHP2Shell: what admins should note now
The php.net interim measures for CVE-2026-109618 almost all revolve around memory and request sizes. They reduce the risk but do not replace the update:
; FPM pool, e.g. /etc/php/8.3/fpm/pool.d/www.conf
php_admin_value[memory_limit] = -1
php_admin_value[post_max_size] = 8M ; 32M if uploads are needed
php_admin_value[upload_max_filesize] = 6M ; keep below post_max_size
Three points are decisive here. First, no application, no directory and no other pool may set a finite limit again; php.net expressly demands this control. Second, a service with memory_limit=-1 needs a limit from outside, via container or cgroup, according to php.net ideally in such a way that only the affected worker is terminated. According to php.net, RLIMIT_AS and RLIMIT_DATA are unsuitable. Third, php.net recommends limits at the web server or proxy for request size, number of fields, multipart parts, headers and nesting depth.
memory_limit=-1 without a memory limit from outside can bring a server to a complete standstill under high load. Apply the interim measures only as a package and only until the update.
How to set the limit with systemd is described under Memory limits with systemd and cgroups; the permanent solution for everyday operation is described in Setting memory_limit correctly.
How does PHP SOS help?
Through the PHP patch service, we proceed as follows:
- Check the inventory: We record every PHP version on the server, separately for CLI, FPM and Apache module, and test whether the fixed version is running.
- Put memory limits in order: We check everywhere
memory_limitis set and configure pool and system limits so that they fit together. - Secure the interim period: Where the update is still blocked, we apply the php.net measures completely and locked against overriding.
- Provide evidence: You receive versions, package states and configuration before and after the intervention in writing.
Frequently asked questions
What does the message “Allowed memory size exhausted” mean?
A script tried to request more memory than memory_limit allows. PHP then aborts the running request with a fatal error. Other requests and the server itself keep running.
Does memory_limit apply to the whole server?
No, the limit applies per PHP process and request. If 20 FPM workers each run with a 256 MB limit, together they can theoretically occupy over 5 GB. The overall limit must therefore be set via pool size, systemd or containers.
What is memory_hard_limit_percent?
A new directive that arrived with PHP 8.5.12, 8.4.27, 8.3.36 and 8.2.35. It sets a hard limit as a percentage of memory_limit that is never exceeded. Since then, memory_limit itself is a soft limit.
Does memory_limit=-1 protect against PHP2Shell?
Only partly. php.net names the setting as an interim measure against CVE-2026-109618, expressly without complete protection. It absolutely requires a memory limit from outside, and the update remains mandatory.