PHP memory_limit for Magento 2: Configuration, Errors, and Troubleshooting
There is no single universal memory_limit value for Magento 2 that works equally well for the storefront, Admin, cron, indexers, and bin/magento commands. The limit applies to an individual PHP process, while a regular HTTP request, catalog import, and setup:di:compile can have very different memory peaks.
A typical situation looks confusing: php.ini already contains 2G, the store opens normally, but setup:di:compile still fails with Allowed memory size exhausted. Or the opposite happens: all CLI commands work, while a large import in Magento Admin consistently hits a 512 MB limit. In the first case, the wrong PHP configuration is often being checked; in the second, the wrong execution context.
PHP Fatal Error and a system OOM must not be treated as the same problem either. PHP can exhaust its own 512 MB limit while several gigabytes of RAM are still available on the VPS. At the same time, a process with a very high or unlimited memory_limit can be killed by Linux, a container, or systemd before PHP has a chance to write the usual Fatal Error.
Troubleshooting should therefore start with identifying the process that is actually failing: PHP-FPM, CLI, cron, an indexer, or a consumer. Then determine the loaded php.ini, additional configuration files, and the effective limit. Only after that does it make sense to change the setting and repeat the same Magento operation.
How much PHP memory_limit does Magento 2 need, and why is one value not enough?
For Magento 2, around 1 GB is a reasonable starting point for normal production PHP workloads, but it should be treated only as a baseline. Heavy CLI operations, debugging, and testing may require a higher limit. At the same time, a large memory_limit should never be configured without considering the total amount of RAM available on the server.
memory_limit restricts the memory available to an individual PHP process. If it is set to 1G, every PHP-FPM worker does not immediately reserve one gigabyte. One request may use 100–200 MB, while another may consume significantly more. That is why saying “Magento needs 2 GB” is technically too vague: you first need to know which Magento operation is running.
| Scenario | Starting point | What to consider | Where to check |
|---|---|---|---|
| Storefront and regular Magento Admin | Around 1G as a starting point | Extensions, heavy Admin pages, catalog size, imports | PHP-FPM and the effective web memory_limit |
| Magento CLI and cron | Around 1G to start | Indexers, cron jobs, imports, specific CLI commands | CLI php.ini and the PHP executable path |
setup:di:compile and static content deploy |
Start with 1G and test with a higher limit if exhaustion occurs | Number of modules, themes, store views, and the specific deployment stage | PHP CLI and the actual process peak |
| Debugging | Around 2G may be required | Profiling and additional debugging tools increase memory usage | Only the relevant PHP context |
| Heavy testing scenarios | Sometimes 3–4G may be required | Do not automatically transfer a testing limit to production | Server RAM, RSS, and system-level limits |
These values do not mean that every Magento store should use exactly these limits. The point of a baseline is to avoid two extremes: 128–256 MB, which can quickly become too restrictive for a heavy Magento project, and blindly assigning 4G or -1, which may allow PHP to consume almost everything the operating system can provide.
The storefront and setup:di:compile do not need to use the same limit either. If the store works reliably with a moderate PHP-FPM ceiling, there is no reason to increase it just because an occasional deployment command needs more memory. CLI can use a separate php.ini, or the limit can be changed temporarily for one specific command.
A common support scenario looks like this: the storefront works for weeks, then after an extension update the deployment starts failing during compile. Increasing the limit for all PHP-FPM workers is premature. Check CLI first. PHP-FPM may have nothing to do with the failure.
bin/magento, cron, or an indexer fails, start with PHP CLI. Only then choose a permanent memory_limit.
How do you distinguish PHP memory_limit from insufficient VPS RAM?
Allowed memory size exhausted means PHP reached its own configured limit. A system OOM means memory was exhausted or restricted at the Linux, container, or service-unit level. These situations require different fixes.
For example, with memory_limit=512M, PHP can terminate a Magento process even though free -h still shows several gigabytes of available RAM. That is expected: PHP reached its own ceiling. If the kernel kills the process through the OOM Killer, increasing the PHP limit may only make the situation worse.
Allowed memory size of ... bytes exhausted— check PHPmemory_limitfirst.Out of memory: Killed process ...— the operating system killed the process.Cannot allocate memory— check available RAM and environment limits.- The process simply disappears without a PHP Fatal Error — check kernel OOM, cgroup, or systemd limits.
Start with these commands:
free -h
ps aux --sort=-%mem | head
journalctl -k
dmesg | grep -i -E "out of memory|killed process|oom"
In free -h, look not only at free but also at available memory and swap usage. In ps, identify processes that actually consume large amounts of RAM. In the kernel log, look for oom, killed process, or the name of the terminated php, php-fpm, mysqld, or another service.
At the same time, you can search Magento logs for PHP exhaustion:
grep -R "Allowed memory size" var/log/
The most useful files are usually var/log/exception.log, var/log/system.log, and var/log/cron.log. If they contain a Fatal Error with the exact allowed memory size, that is strong evidence that PHP hit its own ceiling first.
What should you check if there is no PHP Fatal Error?
The absence of Allowed memory size exhausted does not rule out a memory problem. The process may have been terminated before PHP had a chance to write the error. This is especially relevant for Docker, LXC, systemd services, and other environments with separate resource limits. On shared hosting, similar per-account resource limits are often handled through CloudLinux; the article “What CloudLinux is used for” explains that layer in more detail.
If Magento runs in Docker, first check current usage and the configured container limit:
docker stats --no-stream
If the container has a memory limit, PHP inside it may report memory_limit=2G, while the container can still be terminated much earlier if its own limit is lower or other processes inside the container consume the available memory.
For cgroup v2 on Linux, start with:
cat /proc/self/cgroup
cat /sys/fs/cgroup/memory.current
cat /sys/fs/cgroup/memory.max
If those files are not available, the server may use a different cgroup layout or the process may not be in the root group. In that case, inspect /proc/self/cgroup first and determine the actual path from there.
For systemd, memory restrictions are checked for the specific service. First find the PHP-FPM service name:
systemctl list-units --type=service --all | grep -Ei 'php.*fpm'
Then check the discovered service:
systemctl show php8.3-fpm \
--property=MemoryCurrent \
--property=MemoryHigh \
--property=MemoryMax
php8.3-fpm is only an example service name. Your server may use a different one.
Why does process RSS not have to match memory_limit?
memory_limit and RSS measure different things. PHP limits memory tracked by its own allocation mechanism, while RSS shows the memory pages currently resident for the process. RSS can include libraries, mapped files, and other areas, and some pages may be shared between processes.
So a situation where memory_limit is 512M while RSS looks somewhat different does not automatically mean the measurement is wrong. For PHP exhaustion, rely on the Fatal Error and ini_get(); for VPS capacity planning, use actual system metrics.
memory_limit=-1 does not simplify this distinction. It removes the PHP safety ceiling. If a Magento process keeps growing uncontrollably, the next limit may be the VPS RAM itself. Instead of a clean PHP Fatal Error, you may end up with a system OOM. That is worse.
How do you check the actual memory_limit for Magento 2 in web, CLI, and cron?
Check the effective value in the PHP process that actually runs the failing Magento operation, not merely a line in a php.ini file you happened to find. PHP-FPM, CLI, and cron can easily use different configurations on the same server.
How do you check memory_limit in PHP CLI?
For bin/magento, start with the effective value:
php -r 'echo ini_get("memory_limit"), PHP_EOL;'
Then determine which PHP executable is running and which configuration files it reads:
which php
php -v
php --ini
php -i | grep '^memory_limit'
php --ini is especially useful because it shows both the main php.ini and the directory containing additional configuration files. The output may look like this:
Configuration File (php.ini) Path: /etc/php/8.x/cli
Loaded Configuration File: /etc/php/8.x/cli/php.ini
Scan for additional .ini files in: /etc/php/8.x/cli/conf.d
Additional .ini files parsed: /etc/php/8.x/cli/conf.d/...
The paths are only examples. On a real server they depend on the distribution, PHP version, and control panel. For troubleshooting, focus on three lines: Loaded Configuration File, the additional ini directory, and the list of files that were actually parsed.
If the main php.ini contains 2G but a later file under conf.d sets 512M, the effective value can differ from what the administrator sees in the first file.
How do you check PHP-FPM in one request?
CLI tells you nothing about PHP-FPM. For the web context, instead of exposing a full phpinfo(), create a small temporary diagnostic file:
<?php
header('Content-Type: text/plain; charset=UTF-8');
echo 'SAPI: ', PHP_SAPI, PHP_EOL;
echo 'memory_limit: ', ini_get('memory_limit'), PHP_EOL;
echo 'loaded_ini: ', php_ini_loaded_file() ?: 'none', PHP_EOL;
echo 'scanned_ini: ', php_ini_scanned_files() ?: 'none', PHP_EOL;
Open the file through the same domain and virtual host that serves Magento 2. This single check answers four questions:
- which SAPI handles the request;
- which
memory_limitis actually active; - which main
php.iniis loaded; - which additional ini files were parsed.
Delete the file after testing. Even a small diagnostic endpoint should not remain publicly accessible without a reason.
A common situation is simple: SSH reports 2G, while the web file reports 512M. At that point the problem is already much narrower. Magento is not “caching” the old value — CLI and FPM are simply reading different configurations.
How do you check which PHP runs Magento cron?
Magento cron normally runs through PHP CLI, but you should not automatically assume it is the same php executable used in your interactive SSH session.
crontab -l
Check the full path to PHP in the cron entry. If an explicit executable is used, test that exact binary:
/usr/bin/php -r 'echo ini_get("memory_limit"), PHP_EOL;'
/usr/bin/php --ini
If cron uses another path, substitute it. This is especially important on servers with multiple PHP versions: your interactive shell may resolve one version through PATH, while crontab calls another by its full path.
- PHP SAPI;
- the PHP executable path for CLI;
- Loaded Configuration File;
- additional ini files;
- the effective
memory_limit.
After that, you know which configuration needs to be changed. Before that, editing random php.ini files is just guesswork.
Where should you change memory_limit for Magento 2: php.ini, .user.ini, or the PHP-FPM pool?
For Magento 2, the correct place to change memory_limit depends on the SAPI and the scope of the setting. php.ini, .user.ini, PHP-FPM pool settings, and the -d option solve different problems and may all exist at the same time.
| Configuration source | Where it applies | What can override or affect it | How to verify |
|---|---|---|---|
CLI php.ini |
bin/magento, cron, and other CLI processes |
conf.d/*.ini, the -d option, another PHP executable |
php --ini and ini_get() |
FPM php.ini |
Web requests handled by the corresponding FPM | conf.d, pool settings, .user.ini |
Diagnostic web file |
conf.d/*.ini |
Depends on the SAPI and scanned directory | File loading order and higher-priority settings | php --ini or php_ini_scanned_files() |
.user.ini |
CGI/FastCGI web context | FPM admin settings and reload delay | Web ini_get() |
FPM php_value |
A specific FPM pool | Depends on directive type and configuration | Web ini_get() |
FPM php_admin_value |
A specific FPM pool | Administrative server-level restriction | Web ini_get() and pool configuration |
php -d memory_limit=... |
One CLI invocation | Applies only to that command | Result of the specific command |
What does a regular php.ini setting look like?
memory_limit = 1G
After changing the CLI php.ini, a new CLI process normally reads the setting on its next run. If you changed the FPM php.ini, the running workers need to receive the updated configuration through a reload or restart of the corresponding PHP-FPM service.
When should you use .user.ini?
.user.ini works for CGI/FastCGI web requests and is especially useful when you do not have root access, for example on regular Linux hosting. Magento CLI does not use it, so this setting:
memory_limit = 1G
will not fix bin/magento setup:di:compile and will not affect cron when cron uses PHP CLI.
Another detail is that .user.ini changes may not be picked up immediately. After changing it, verify the effective value through web ini_get() rather than trusting the file itself.
How is memory_limit configured at the PHP-FPM pool level?
A pool may contain:
php_value[memory_limit] = 1G
or the administrative form:
php_admin_value[memory_limit] = 1G
Treat php_admin_value as a strict server-side setting: application code should not expect to override it freely with ini_set(). That is why Magento may still see 512M even if a local ini file or application code tries to set a higher value.
The situation “.user.ini says 2G but Magento still gets 512M” is exactly when you should inspect the PHP-FPM pool and the effective value. Trust what the running PHP process sees, not the nicest-looking configuration file.
.htaccess is not universal either. Older instructions using php_value memory_limit apply only to certain Apache/PHP handlers. If the site runs through PHP-FPM, copying such a recipe without checking the handler may do nothing.
Why does Magento 2 still show the old memory_limit after changing php.ini?
If the file already contains 2G but Magento still fails around 512 MB, you usually need to look for a different SAPI, a different loaded ini file, or an override. Editing a file by itself proves nothing.
Follow one chain instead of editing five configuration files at once.
- Identify PHP. For CLI, run
which phpandphp -v. - Find the loaded ini. Run
php --ini. - Check additional ini files. Look at
Scan for additional .ini filesandAdditional .ini files parsed. - Get the effective limit. Run
php -r 'echo ini_get("memory_limit"), PHP_EOL;'. - Repeat the check through PHP-FPM for web requests. The CLI result does not automatically apply to the website.
- Inspect the FPM pool and .user.ini. Pay particular attention to
php_valueandphp_admin_value. - Apply the FPM configuration. If you changed the FPM ini or pool settings, reload or restart the relevant service.
- Check the effective value again. Only then repeat the failing Magento operation.
Read the output of php --ini literally. For example:
Loaded Configuration File: /etc/php/8.x/cli/php.ini
Scan for additional .ini files in: /etc/php/8.x/cli/conf.d
Additional .ini files parsed: /etc/php/8.x/cli/conf.d/...
If you edited an FPM configuration file but the output points to a cli directory, you are checking the wrong PHP context. And vice versa.
Before searching for overrides, determine the actual configuration directories and only then run grep. For example, if php --ini shows /etc/php/8.3/cli:
grep -Rni --include='*.ini' 'memory_limit' /etc/php/8.3/cli
FPM will use a different path. On a server with a control panel or a non-standard PHP build, it may not even be under /etc/php, so identify the configuration first and search second.
Restarting Nginx is not a magic fix here either. If the PHP-FPM php.ini changed, PHP-FPM itself needs to reload the configuration.
Control-panel servers have another unpleasant scenario: an administrator manually edits a generated file, everything works, and then saving settings in the panel restores the old value. In that case, change the configuration source managed by the panel, not the generated file.
What should you do if setup:upgrade, setup:di:compile, or static-content:deploy runs out of memory?
If the storefront works but Magento 2 deployment commands fail because of memory exhaustion, check PHP CLI. These processes do not run through PHP-FPM, so the web limit may be perfectly fine and completely unrelated to the failure.
Start by checking the current CLI configuration:
php --ini
php -r 'echo ini_get("memory_limit"), PHP_EOL;'
php -v
Then, to test the hypothesis, temporarily increase the limit for just one command:
php -d memory_limit=2G bin/magento setup:di:compile
The same approach works for other CLI operations:
php -d memory_limit=2G bin/magento setup:upgrade
php -d memory_limit=2G bin/magento setup:static-content:deploy
2G in these examples is not a requirement for every Magento 2 installation. It is a controlled test: if the command consistently fails with the old limit but succeeds with a higher temporary limit, the PHP ceiling is very likely involved.
After a successful run with 2G, check the actual process peak rather than only the allowed limit:
/usr/bin/time -v php -d memory_limit=2G bin/magento setup:di:compile
Near the end of the output you will see a line similar to:
Maximum resident set size (kbytes): ...
The exact number depends on the store, so there is no useful “typical” value to insert here. What matters is how close the actual RSS gets to the configured limit and what happens to the VPS memory at the same time.
free -h
If compile succeeds and the server still has a healthy amount of available RAM, that is one scenario. If the command succeeds only because it consumes nearly all available memory and pushes the VPS heavily into swap, a permanent 2G limit is not a good configuration.
When does high memory usage during compile become suspicious?
Suppose the project used to compile with 1G. After an extension update, compile starts reaching 1G and failing. You raise the limit to 2G, and the process reaches almost 2G and fails again. At that point the ceiling is no longer fixing the cause.
Compare changes in modules, DI configuration, custom code, and the environment. Check whether the issue reproduces on staging, whether it started after a specific update, and at which compile stage memory begins to grow.
This still does not prove a memory leak in an extension. But the direction is now different: stop adding memory and start profiling.
Should you keep a high CLI memory_limit permanently after compile succeeds?
Not necessarily. If a higher limit is needed only for an occasional deployment, you can keep a more moderate PHP-FPM ceiling and configure CLI separately. This avoids giving several parallel web workers an unnecessarily high allowance just because of a command that runs only occasionally.
Why can Magento 2 cron jobs and indexers fail while the store still works?
A working storefront does not prove that Magento cron is healthy. Background tasks normally run through PHP CLI, which means they may use a different memory_limit, a different PHP executable, and a very different memory profile.
The store opens. Admin works too. The next morning you discover that the catalog index was not updated. In that situation, the first things to inspect are not the FPM configuration but cron.log, the state of cron_schedule, and the PHP executable used by cron.
Start with indexer status:
bin/magento indexer:status
bin/magento indexer:info
If you already know which indexer is failing, reproduce that one separately:
bin/magento indexer:reindex catalogsearch_fulltext
catalogsearch_fulltext is only an example. Use indexer:info to get the actual identifiers first.
To test cron groups:
bin/magento cron:run --group index
bin/magento cron:run --group default
If the error reproduces manually, immediately check the CLI memory_limit using the same PHP executable:
php -r 'echo ini_get("memory_limit"), PHP_EOL;'
php --ini
How do you check errors directly in cron_schedule?
Instead of a vague recommendation to “look at cron_schedule,” run a safe SELECT query. It does not change anything in the database:
SELECT
job_code,
status,
scheduled_at,
executed_at,
finished_at,
messages
FROM cron_schedule
WHERE status IN ('error', 'missed')
ORDER BY scheduled_at DESC
LIMIT 50;
Look at job_code, execution time, and messages. Then correlate the event with var/log/cron.log, exception.log, and system logs. This helps determine whether the problem really belongs to a particular cron task rather than merely occurring at the same time.
Do not “fix” cron by mass-deleting records from cron_schedule. The table is being used here as a diagnostic source.
Why can an indexer start failing after the catalog grows?
An indexer may process far more data than a single storefront request. After a bulk update of products, prices, or attributes, the amount of work can increase sharply. But “the catalog is large, so we need 4G” is not a useful diagnosis.
Identify the specific indexer first, then reproduce it separately and measure the process:
/usr/bin/time -v php bin/magento indexer:reindex catalogsearch_fulltext
If only one indexer has the problem, investigate the data and extensions involved in that particular indexing path.
What should you do if memory grows in a long-running consumer?
Magento queue consumers are a separate case. Cron usually starts relatively short-lived tasks, while a consumer may stay alive and process messages for a long time. If its RSS gradually increases for hours, a single cron:run test will not reveal the problem.
List consumers:
bin/magento queue:consumers:list
Then locate the running process:
ps -eo pid,rss,etime,cmd --sort=-rss | grep '[q]ueue:consumers:start'
Check not only RSS but also etime, which shows how long the process has been running. If two identical consumers start with moderate memory usage but one consistently grows after several hours, that is a useful signal to inspect message handlers and third-party modules.
For diagnostics, you can run a consumer with a limited number of messages if the Magento version in use supports the corresponding option, then compare memory usage between a short and a long run. The main point is not to mix this scenario with a regular PHP-FPM request.
The storefront can be healthy while background processing fails. Magento allows that situation quite easily.
Which Magento Admin operations commonly reveal an insufficient memory_limit?
If Magento Admin fails only during an import, a mass action, or a specific page request, troubleshoot that HTTP request first. Raising the PHP limit globally just because one operation is heavy is premature.
Common trouble spots include:
- large CSV imports;
- mass product actions;
- heavy Admin grids;
- saving entities with large amounts of related data;
- third-party extension pages;
- operations that became noticeably heavier after an extension update.
How do you troubleshoot an Admin import that runs out of memory?
Suppose normal Admin pages work, a small file imports successfully, but a large CSV ends with a Fatal Error. Instead of immediately assigning 4G globally, reproduce the problem as a controlled test.
- Record the exact time when the import starts.
- Check Magento logs and the PHP-FPM error log for that time window.
- Find
Allowed memory size exhaustedif it is present. - Check the web
memory_limitthrough the same FPM. - Repeat the import with a smaller dataset.
- Compare how PHP-FPM worker memory usage changes.
Running processes can be inspected with:
ps -eo pid,rss,etime,cmd --sort=-rss | grep '[p]hp-fpm'
If your process name is different, locate it with a regular ps aux. On a server with version-specific PHP executables, the process line may include the exact version.
If a small batch consistently succeeds and memory usage grows with the amount of input data, inspect the import mechanism and batch size. If even a small dataset suddenly causes an abnormal spike after installing a new extension, the investigation should focus on that module instead.
What should you do if only one Admin grid has the problem?
A common pattern is that the dashboard opens quickly, the catalog works, but one particular grid becomes extremely slow or runs out of memory after an extension update. That is already a useful filter.
Correlate the request time with the PHP-FPM log and Magento exception log. If APM is available, find the exact transaction and see where memory usage rises. Check whether the extension loads an excessively large collection before pagination is applied or performs heavy calculations for every row.
In a test environment, you can selectively use memory_get_peak_usage() or a profiler. In production, start with logs, APM, and system metrics rather than adding debugging code directly to a live Magento installation.
If raising the limit from 512M to 1G fixes the issue and repeated requests use a controlled amount of memory, that may be an acceptable result. If the page consumes almost 1G with a 1G limit and then nearly 2G after the limit is raised to 2G, stop increasing the ceiling. Find what is retaining the memory.
Why can an excessively high memory_limit bring down a Magento 2 VPS?
A high memory_limit allows an individual PHP process to become larger, but it does not add physical RAM to the server. Magento shares memory with other PHP-FPM workers, CLI processes, cron, MySQL, Redis, OpenSearch, and the operating system.
That is why this formula:
pm.max_children × memory_limit = PHP-FPM memory usage
is not a valid calculation of actual RAM usage. memory_limit is an upper bound for PHP allocation, while real workers have different RSS values. But the opposite mistake is dangerous too: a high ceiling allows several heavy workers to grow much larger at the same time.
How do you check the actual RSS of PHP-FPM workers?
ps -eo pid,rss,etime,cmd --sort=-rss | grep '[p]hp-fpm'
RSS is shown in kilobytes. Focus on pool workers, not only the master process. A single snapshot says little, so repeat the command during normal traffic and while reproducing the problematic request.
For a quick summary of php-fpm: pool processes, you can use:
ps -eo rss,args | awk '
/php-fpm: pool/ {
sum += $1;
n++;
if ($1 > max) max = $1
}
END {
if (n > 0)
printf "workers=%d avg=%.1fMB max=%.1fMB\n",
n, sum/n/1024, max/1024;
}'
If your PHP build uses another process naming format, adjust the filter. Do not get workers=0 and conclude that PHP-FPM uses no memory.
How do you estimate a safe pm.max_children value?
First estimate how much RAM can realistically be allocated to PHP-FPM:
VPS RAM
- operating system
- MySQL
- Redis
- OpenSearch
- other persistent services
- reserve for cron, CLI, and spikes
= PHP-FPM memory budget
Then use the observed upper working RSS of a worker rather than memory_limit:
approximate worker limit =
PHP-FPM RAM budget /
observed upper working RSS of one worker
This is not an exact formula to copy directly into pm.max_children. RSS includes shared-memory effects, and workload changes over time. Do not fill the calculated capacity completely: the server still needs headroom for simultaneous cron tasks, deployment, MySQL spikes, and occasional heavy requests.
If the arithmetic says the server can support a certain number of workers only under ideal average RSS, do not treat that number as guaranteed safe. Load testing and observation matter more than the formula itself.
| What you observe | What it may mean | Next step |
|---|---|---|
PHP repeatedly fails near the same memory_limit |
The PHP ceiling was reached | Investigate the specific Magento operation |
| Several FPM workers have high RSS at the same time | Concurrency is creating memory pressure | Check pm.max_children and heavy requests |
| Available RAM drops sharply during cron | A background task creates a memory spike | Measure the specific CLI process |
| Swap is constantly used under load | The server is short on RAM | Recalculate the overall memory budget |
| The kernel kills php-fpm or mysqld | System OOM | Reduce memory pressure or add RAM |
| No PHP Fatal Error, container shows OOMKilled | The cgroup/container limit was reached | Check the container memory limit |
When does increasing memory_limit require a PHP-FPM review?
If the VPS already uses swap, Magento shares the server with OpenSearch and MySQL, and PHP-FPM workers have high RSS during peaks, increasing memory_limit without checking pm.max_children is risky.
Imagine a small server where several heavy Admin requests arrive while an indexer starts at the same time. Previously PHP limited every process with a relatively low ceiling. After setting a very high limit, all of those processes are allowed to grow simultaneously. PHP Fatal Errors become less frequent. Then the OOM Killer appears.
That is not an improvement.
Setting 4G in php.ini on a VPS with 4 GB of RAM does not give Magento “4 GB of spare memory.” The ceiling of one process is not the memory budget of the entire server.
How do you choose a safe memory_limit for Magento 2 and verify that the problem is really fixed?
A good memory_limit is one that has been validated with a specific Magento workload and does not create system-wide memory pressure. The disappearance of Allowed memory size exhausted alone does not prove that the configuration is safe. If you are testing the setup on a new VPS, it is useful to plan a limited-time server memory test that includes compile, cron, imports, and peak web requests instead of judging the server only by whether the homepage opens.
- Identify the process. Storefront, Admin, CLI, cron, indexer, consumer, or deployment.
- Record the effective limit. Get the SAPI, loaded ini, and
ini_get("memory_limit"). - Reproduce the error. Use the same operation each time.
- Increase the limit in a controlled way. For CLI,
php -d memory_limit=...is convenient. - Repeat the task. Confirm that the original error is actually gone.
- Measure system memory. Check RSS,
free -h, swap, and kernel logs. - Make the setting permanent only after another verification run.
How should you interpret the test result?
| Result | What it means | What to do next |
|---|---|---|
| The task runs reliably after increasing the limit and RSS remains controlled | The previous PHP ceiling was genuinely too low | Keep the tested value with reasonable headroom |
| The error disappears but the VPS starts swapping heavily | PHP gained memory at the expense of the entire server | Review the memory budget, FPM concurrency, or available RAM |
| The process fails near 1G with a 1G limit and near 2G after raising it to 2G | Memory consumption keeps growing | Investigate code, data, modules, or the algorithm |
| No PHP Fatal Error appears, but the process is killed | System OOM, cgroup, or systemd limits are likely | Check kernel, container, or service logs |
| The problem occurs only in cron or an indexer | The web memory_limit may be unrelated | Check PHP CLI and the specific task |
| The problem occurs only in one Admin operation | A specific heavy request is responsible | Profile that request instead of the entire Magento installation |
When does increasing memory_limit stop looking like a solution?
If the process keeps reaching almost every new limit, PHP no longer needs more tuning — you need to find what is retaining the memory.
Be especially suspicious when:
- the same operation used to work with a lower limit;
- the problem appeared after installing or updating an extension;
- abnormal usage occurs only in one indexer or Admin request;
- a consumer keeps growing during a long-running session;
- the error depends on a particular imported dataset;
- staging completes the same task while production fails under a similar configuration.
At that point, inspect third-party Magento modules, custom code, large collections, import batch sizes, observers, plugins, recursive calls, and queue handlers. If APM or a profiler is available, it is more useful here than another increase in memory_limit.
- the original symptom is known precisely;
- you know whether it was a PHP Fatal Error or a system OOM;
- the correct PHP SAPI has been checked;
- the effective
memory_limithas been confirmed; - the failing Magento task now completes reliably;
- the VPS does not enter dangerous swap usage or OOM;
- if memory usage keeps growing, further limit increases stop and profiling begins.
If one increase gives the task enough headroom, repeated runs remain stable, and the server keeps a healthy memory budget, the value can be made permanent. If each new ceiling is almost completely consumed again, continuing to raise memory_limit is pointless. The bottleneck is no longer the number in the PHP configuration.


