That “Allowed memory size exhausted” fatal isn’t just annoying. It’s the kind of failure that shows up at 2 a.m., right after a plugin update, and right before a campaign launch. The site works… until it doesn’t. Then it takes your admin, checkout, or cron down with it.
The fix is rarely “just bump wp-config.” WordPress memory is a stack of limits across PHP, PHP-FPM, containers, cgroups, and sometimes your hosting provider’s invisible ceiling. Raise the wrong one and nothing changes. Raise the right one without understanding why you need it and you’ll hide a leak until it becomes an outage.
What the error really means (and what it doesn’t)
The canonical WordPress crash looks like this:
- “Fatal error: Allowed memory size of X bytes exhausted (tried to allocate Y bytes)”
That message is from PHP, not WordPress. WordPress is just the messenger who caught the brick. PHP hit a hard ceiling and aborted the request. The ceiling is usually memory_limit, but the underlying cause might be very different:
- A plugin/theme pulling too much data into memory (common: WooCommerce reports, page builders, importers).
- A loop or recursion bug creating ever-growing arrays/objects.
- An image processing path using Imagick/GD and holding multiple full-size bitmaps.
- OPcache fragmentation or a tiny OPcache in a large plugin ecosystem (can look like random slowness or 502s rather than “memory exhausted,” but it’s the same class of failure).
- A container or VM being killed by the kernel OOM killer (no friendly PHP fatal; just a dead worker).
- A hosting plan cap: you raise PHP memory, but the provider throttles the process or kills it anyway.
Here’s the key: raising memory is not a victory condition. It’s a tool. You raise memory to (a) get back online safely, then (b) make the request’s memory profile sane so you can lower it again. If you only do step (a), you’ve postponed the outage and made it weirder.
One quote to keep you honest: “Hope is not a strategy.”
(General operations maxim, often attributed to various engineering leaders; paraphrased idea.)
Interesting facts and context (because this problem has history)
- PHP’s default memory_limit used to be small. Older PHP versions commonly shipped with 128M, which was fine for blogs and hilariously insufficient for modern WordPress stacks.
- WordPress has two “memory” concepts. The
WP_MEMORY_LIMITandWP_MAX_MEMORY_LIMITconstants can request higher limits, but they can’t exceed what PHP allows. - WooCommerce changed expectations. Ecommerce turned “a site” into “an application,” and admin/reporting workloads became data-heavy enough to need serious memory.
- PHP-FPM made per-pool limits normal. Before PHP-FPM dominated, tuning per-site limits was messier; now you can isolate sites with pool configs (when you control the host).
- OPcache became a default performance feature. It also introduced a new failure mode: code cache memory exhaustion and fragmentation that can look like random breakage.
- Containers changed the game. A container can have plenty of PHP memory_limit but still be OOM-killed because the cgroup limit is lower.
- Shared hosting adds invisible ceilings. Providers often impose per-account memory or process limits; raising
memory_limitcan be ceremonial. - WordPress cron isn’t “real cron.” WP-Cron is triggered by web traffic; when it runs heavy tasks inside requests, memory spikes happen at inconvenient times.
One short joke, because we’ve earned it: WordPress memory errors are like office coffee—when you finally notice the problem, it’s already too late and everyone’s mad.
Fast diagnosis playbook (check first/second/third)
This is the “stop guessing” checklist. You can do it in under 15 minutes on most systems.
First: is it PHP memory_limit, or the OS/container killing you?
- If you see the explicit PHP fatal “Allowed memory size exhausted,” it’s almost always
memory_limit(or a WordPress constant failing to raise it). - If you see 502/504 with no PHP fatal, check PHP-FPM logs and kernel/cgroup OOM events. That’s a different lane.
Second: which SAPI and config is actually used (FPM vs CLI)?
php -iis CLI. Your web requests might use a differentphp.iniunder PHP-FPM.- Use an endpoint or logs to confirm runtime settings for the web SAPI.
Third: identify the request path and culprit
- Is it only wp-admin? Only checkout? Only WP-Cron? Only one endpoint?
- Turn on targeted logging, reproduce once, and grab the stack trace or plugin list that correlates with the spike.
Fourth: choose the right remediation tier
- Emergency: raise the effective limit at the controlling layer to restore service.
- Stabilize: isolate with PHP-FPM pool tuning, reduce concurrency if needed.
- Fix root cause: plugin/theme bug, query size, import batch size, caching strategy, WP-Cron schedule.
Where memory limits live: the stack that decides your fate
Think of WordPress memory as a set of gates. The smallest gate wins. You can paint “512M” on the WordPress gate all day; if PHP’s gate is 128M, you’re still not getting through.
Gate 1: WordPress constants (requests, not guarantees)
WP_MEMORY_LIMIT: intended for front-end.WP_MAX_MEMORY_LIMIT: intended for admin tasks (updates, imports, etc.).
These constants can raise the limit up to PHP’s ceiling, depending on whether PHP allows changing memory_limit at runtime.
Gate 2: PHP runtime setting (the hard ceiling)
memory_limit is a PHP setting, set via php.ini, FPM pool config, Apache config, or per-directory overrides (depending on SAPI and permissions). This is usually the setting that produces the “Allowed memory size exhausted” message.
Gate 3: PHP-FPM pool configuration (per-site isolation)
If you operate your own host (VM/bare metal), PHP-FPM pools let you set per-site limits and behaviors. You can enforce memory_limit via php_admin_value[memory_limit] (not overrideable by scripts), or allow php_value (overrideable).
Gate 4: Process manager constraints (concurrency)
Memory exhaustion isn’t just “one request is big.” It’s often “many requests are moderately big.” FPM settings like pm.max_children decide how many workers can run concurrently. If each worker can use up to 512M, and you allow 20 workers, you’ve just promised the OS 10GB of memory. The OS will not be impressed.
Gate 5: The OS, cgroups, and the OOM killer
On containers and many managed platforms, the actual limit is the cgroup memory limit. The kernel can kill PHP-FPM workers (or the whole container) without a PHP fatal. In Kubernetes you’ll see OOMKilled. On systemd-managed services you may see memory accounting and enforcement.
Gate 6: Hosting provider policy limits
Shared hosts and some managed WordPress platforms enforce per-account caps (RSS, CPU seconds, entry processes). You might successfully set memory_limit to 512M and still get killed or throttled because the provider’s cap is lower or because your plan forbids it.
Practical tasks (commands, outputs, and decisions)
These are production-grade checks. Each includes: command, sample output, what it means, and the decision to make.
Task 1: Confirm the PHP version and SAPI used by CLI (baseline)
cr0x@server:~$ php -v
PHP 8.2.12 (cli) (built: Oct 12 2024 10:11:21) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.2.12, Copyright (c) Zend Technologies
with Zend OPcache v8.2.12, Copyright (c), by Zend Technologies
Meaning: This is CLI PHP. Useful for WP-CLI jobs, not definitive for web requests.
Decision: If the error occurs via web traffic, don’t assume this config applies. Move on to FPM/web checks.
Task 2: Find which php.ini CLI is using (avoid config mirages)
cr0x@server:~$ php --ini
Configuration File (php.ini) Path: /etc/php/8.2/cli
Loaded Configuration File: /etc/php/8.2/cli/php.ini
Scan for additional .ini files in: /etc/php/8.2/cli/conf.d
Additional .ini files parsed: /etc/php/8.2/cli/conf.d/10-opcache.ini
Meaning: You now know where CLI reads config from.
Decision: If WP-CLI is failing with memory exhausted, you can tune this. If web fails, you need FPM config next.
Task 3: Read the effective memory_limit for CLI
cr0x@server:~$ php -r 'echo ini_get("memory_limit").PHP_EOL;'
128M
Meaning: CLI is capped at 128M.
Decision: If WP-CLI imports or cron jobs fail, raise CLI memory_limit (or run with -d memory_limit=... for one-off tasks).
Task 4: Confirm PHP-FPM service is present and running (web lane)
cr0x@server:~$ systemctl status php8.2-fpm --no-pager
● php8.2-fpm.service - The PHP 8.2 FastCGI Process Manager
Loaded: loaded (/lib/systemd/system/php8.2-fpm.service; enabled; preset: enabled)
Active: active (running) since Fri 2025-12-27 08:41:10 UTC; 2h 11min ago
Docs: man:php-fpm8.2(8)
Main PID: 1042 (php-fpm8.2)
Tasks: 23 (limit: 18963)
Memory: 612.4M
Meaning: FPM is running; you can inspect pool configs and logs.
Decision: If you see huge “Memory:” here and you’re near node limits, review pm.max_children and per-worker memory.
Task 5: Locate the active FPM pool configs
cr0x@server:~$ grep -R "^\[" -n /etc/php/8.2/fpm/pool.d/*.conf
/etc/php/8.2/fpm/pool.d/www.conf:1:[www]
/etc/php/8.2/fpm/pool.d/site.conf:1:[site]
Meaning: You have multiple pools. Great for isolation; also great for confusion.
Decision: Identify which pool serves the WordPress vhost, then tune that pool, not the wrong one.
Task 6: Check the web SAPI memory_limit via FPM pool settings
cr0x@server:~$ grep -R "memory_limit" -n /etc/php/8.2/fpm/pool.d/*.conf
/etc/php/8.2/fpm/pool.d/site.conf:38:php_admin_value[memory_limit] = 256M
Meaning: This pool enforces 256M, and scripts can’t raise it above that because it’s an admin value.
Decision: If WordPress requests 512M but still dies at 256M, this line is why. Change it here and reload FPM.
Task 7: Validate whether the error is in PHP logs (not just the browser)
cr0x@server:~$ sudo tail -n 50 /var/log/php8.2-fpm.log
[27-Dec-2025 10:38:55] WARNING: [pool site] child 2517 said into stderr: "PHP Fatal error: Allowed memory size of 268435456 bytes exhausted (tried to allocate 1048576 bytes) in /var/www/html/wp-includes/class-wpdb.php on line 2345"
Meaning: This is the authoritative record: 256M (268,435,456 bytes) ceiling hit during database work.
Decision: Raise the web memory_limit (or fix the query/plugin). Also consider why wpdb is loading so much (reports, search, huge autoload options).
Task 8: Check for kernel OOM kills (the “no PHP fatal” scenario)
cr0x@server:~$ sudo dmesg -T | tail -n 20
[Sat Dec 27 10:41:02 2025] Out of memory: Killed process 2517 (php-fpm8.2) total-vm:1462256kB, anon-rss:912324kB, file-rss:0kB, shmem-rss:0kB, UID:33 pgtables:3112kB oom_score_adj:0
[Sat Dec 27 10:41:02 2025] oom_reaper: reaped process 2517 (php-fpm8.2), now anon-rss:0kB, file-rss:0kB, shmem-rss:0kB
Meaning: The kernel killed a PHP-FPM worker. This is not a PHP memory_limit event; it’s node/container memory pressure.
Decision: Reduce concurrency (pm.max_children), increase host memory, lower per-worker memory_limit, or fix the request memory usage. If in containers, raise cgroup limits too.
Task 9: Inspect the container cgroup memory limit (Docker / systemd / cgroups v2)
cr0x@server:~$ cat /sys/fs/cgroup/memory.max
536870912
Meaning: The process group is limited to 512MB total. If you set PHP memory_limit to 512M and allow multiple workers, you will get OOM kills.
Decision: Either raise the cgroup limit, or set PHP memory_limit lower (e.g., 256M) and tune FPM concurrency to fit.
Task 10: Measure real memory use per PHP-FPM child (stop guessing)
cr0x@server:~$ ps -o pid,rss,cmd -C php-fpm8.2 --sort=-rss | head -n 8
PID RSS CMD
2519 412980 php-fpm: pool site
2518 356112 php-fpm: pool site
2516 198344 php-fpm: pool site
1042 65244 php-fpm: master process (/etc/php/8.2/fpm/php-fpm.conf)
Meaning: RSS is actual resident memory. You have workers in the 200–400MB range.
Decision: Use the high-percentile RSS (not the best-case) to size pm.max_children. If two workers can already hit 400MB each, don’t run 10 on a 2GB box.
Task 11: Check WordPress’ view of memory (it’s informative, not authoritative)
cr0x@server:~$ wp --path=/var/www/html option get home
https://example.internal
Meaning: WP-CLI can talk to the site codebase. Now you can run diagnostics.
Decision: If WP-CLI itself fails with memory errors, temporarily run it with a higher limit for diagnosis.
cr0x@server:~$ wp --path=/var/www/html --info
OS: Linux 6.5.0-1022-aws #24~22.04.1-Ubuntu SMP
Shell: /bin/bash
PHP binary: /usr/bin/php8.2
PHP version: 8.2.12
php.ini used: /etc/php/8.2/cli/php.ini
WP-CLI root dir: /home/cr0x/.wp-cli
WP-CLI version: 2.10.0
Meaning: Confirms again this is CLI. Useful, but don’t confuse it with web behavior.
Decision: If the production outage is web-only, focus on FPM settings and web logs.
Task 12: Identify heavy plugins quickly (and disable safely if needed)
cr0x@server:~$ wp --path=/var/www/html plugin list --status=active
+--------------------------+--------+-----------+---------+
| name | status | update | version |
+--------------------------+--------+-----------+---------+
| woocommerce | active | available | 8.6.1 |
| elementor | active | none | 3.20.2 |
| wordfence | active | none | 7.11.2 |
| wp-mail-smtp | active | none | 4.4.0 |
+--------------------------+--------+-----------+---------+
Meaning: You have a familiar cast: ecommerce, a page builder, a security plugin. Any of these can increase memory under certain endpoints.
Decision: If you need emergency recovery, disable the likely culprit plugin (one at a time) after taking a snapshot/backup and communicating impact.
Task 13: Check autoloaded options size (silent memory hog)
cr0x@server:~$ wp --path=/var/www/html db query "SELECT ROUND(SUM(LENGTH(option_value))/1024/1024,2) AS autoload_mb FROM wp_options WHERE autoload='yes';"
+------------+
| autoload_mb|
+------------+
| 12.47 |
+------------+
Meaning: 12.47MB of autoloaded options is high. Autoload options are loaded on many requests, increasing baseline memory.
Decision: Audit large options, reduce autoload where safe, and consider object caching.
Task 14: Find top autoloaded options (targets for cleanup)
cr0x@server:~$ wp --path=/var/www/html db query "SELECT option_name, ROUND(LENGTH(option_value)/1024/1024,2) AS mb FROM wp_options WHERE autoload='yes' ORDER BY LENGTH(option_value) DESC LIMIT 10;"
+------------------------------+------+
| option_name | mb |
+------------------------------+------+
| _transient_timeout_wc_report | 3.21 |
| elementor_css | 2.44 |
| wordfence_waf_options | 1.18 |
| rewrite_rules | 0.94 |
+------------------------------+------+
Meaning: Transients and builder-generated blobs are living in autoload. That inflates memory every time WordPress boots.
Decision: Fix the source (plugin settings), purge/rebuild where appropriate, and ensure transients aren’t permanently autoloaded.
Task 15: Check OPcache status (when “memory” is really code cache pressure)
cr0x@server:~$ php -r 'print_r(opcache_get_status(false)["memory_usage"]);'
Array
(
[used_memory] => 118901488
[free_memory] => 1562032
[wasted_memory] => 4381656
[current_wasted_percentage] => 3.55
)
Meaning: OPcache has ~1.5MB free. That’s tight. With frequent deploys or many plugins, you can hit a wall.
Decision: Increase opcache.memory_consumption and consider opcache.max_accelerated_files. Also review reload strategy to reduce fragmentation.
Task 16: Confirm current FPM process manager settings (concurrency math)
cr0x@server:~$ grep -nE "^(pm\.|php_admin_value\[memory_limit\])" /etc/php/8.2/fpm/pool.d/site.conf
23:pm = dynamic
24:pm.max_children = 20
25:pm.start_servers = 4
26:pm.min_spare_servers = 4
27:pm.max_spare_servers = 8
38:php_admin_value[memory_limit] = 256M
Meaning: You can run 20 workers, each permitted 256M. That is a theoretical 5GB worst-case allocation, plus overhead.
Decision: On a 2–4GB host, this is an outage waiting to happen. Lower pm.max_children or reduce per-worker memory, then load test.
Task 17: Reload FPM safely after changes (don’t bounce production unless required)
cr0x@server:~$ sudo systemctl reload php8.2-fpm
Meaning: FPM reloads config gracefully; existing requests finish, new ones use updated settings.
Decision: Prefer reload over restart during business hours, unless you need a hard reset (e.g., stuck workers).
Task 18: Verify effective memory_limit via a web request (ground truth)
cr0x@server:~$ curl -sS -H "Host: example.internal" http://127.0.0.1/wp-admin/admin-ajax.php?action=heartbeat | head
0
Meaning: Web is responding. This doesn’t show memory_limit directly, but it’s a quick sanity check after changes.
Decision: If the error was on admin endpoints, now reproduce the exact failing action and monitor logs for memory-related fatals.
Raising limits correctly: recipes by hosting model
Here’s the rule: set limits as close as possible to the layer that enforces them. WordPress constants are fine for nudging. They are not governance.
Scenario A: You control the VM/bare metal (Nginx/Apache + PHP-FPM)
1) Set per-pool memory_limit (preferred)
Edit your pool file (example: /etc/php/8.2/fpm/pool.d/site.conf):
- Use
php_admin_value[memory_limit] = 256M(or 384M/512M) when you want it enforced. - Use
php_value[memory_limit] = 256Mif you want apps to override (usually you don’t).
Then reload FPM and confirm logs stop showing exhaustion.
2) Tune concurrency with pm.max_children
Raising memory_limit without tuning concurrency is how you convert a single-request failure into a full-node OOM event. Do the math:
- Measure real worker RSS (Task 10).
- Pick a safe budget: leave room for OS cache, database, and web server.
- Set
pm.max_childrenso worst-case RSS × children fits in budget.
3) Keep WordPress constants modest (and honest)
In wp-config.php you can set:
define('WP_MEMORY_LIMIT', '256M');define('WP_MAX_MEMORY_LIMIT', '512M');
This helps WordPress ask for more memory on admin tasks without giving every front-end request a blank check. If your PHP-FPM pool enforces 256M, setting WP_MAX_MEMORY_LIMIT to 512M won’t help. That mismatch is your signal: decide whether the admin really needs 512M, or whether the plugin/report should be fixed.
Scenario B: Shared hosting (cPanel-style) with limited control
This is where advice on the internet goes to die. You may not control PHP-FPM pools. Your provider may run suPHP, LSAPI, or their own wrapper.
Order of operations:
- Check what you can change (MultiPHP INI Editor, per-domain PHP settings, etc.).
- Try raising
memory_limitin the provider’s UI (best). - If allowed, add or edit
.user.iniwithmemory_limit = 256M. Beware: changes can take minutes to apply. - If using Apache mod_php and allowed, use
.htaccessdirectives (often disabled on modern hosts). - If none of the above work, accept reality: you’ve hit plan limits. Either optimize, or upgrade/move.
Two warnings:
- Do not blindly set 1024M because you found a forum post from 2016. It won’t fix a provider cap, and it will hide a plugin leak until your account gets throttled.
- Do not assume error logs are complete. Shared hosting can suppress key signals. Use application-level logging when possible.
Scenario C: Docker / Compose
In containers, you have at least two limits: PHP’s memory_limit and the container memory limit. You need both aligned.
What good looks like:
- Container memory limit sized to handle peak concurrency.
- PHP-FPM worker count limited to avoid cgroup OOM kills.
- PHP
memory_limitset below the per-worker “too big to fail” threshold.
If your container is capped at 512MB total, setting PHP memory_limit to 512M is like buying a suitcase exactly the size of the airline’s limit, then adding a brick. It closes right up until it doesn’t.
Scenario D: Kubernetes (WordPress behind Ingress)
In Kubernetes, you must treat memory as a resource contract:
- Requests/limits determine scheduling and enforcement. If limit is low, the pod will be OOMKilled under load.
- Set PHP-FPM concurrency to match what your pod can support.
- Monitor for OOMKilled events, restart loops, and 502 spikes from upstream timeouts.
If you can’t make memory predictable, you likely have a plugin endpoint that is unbounded: too many rows pulled, too much generated HTML, or an export that tries to build a full file in RAM.
When raising limits is the wrong fix: plugins, themes, and real leaks
Most “memory exhausted” incidents start with a change: a plugin update, a new report, a bulk import, or an innocent-looking theme function that runs on every request.
How to tell you’re masking a leak
- Memory usage grows gradually over time across workers, not just during one endpoint.
- The same request sometimes succeeds, sometimes fails, depending on cached state or OPcache/code paths.
- Raising memory “fixes” it for days, then it returns worse.
Typical culprits (and the pattern behind them)
- Report builders: loading whole tables into PHP arrays. Fix is pagination, query limits, streaming exports, or moving work to async jobs.
- Importers: reading entire CSV/XML into memory. Fix is chunked processing and lower batch sizes.
- Page builders: massive post meta and JSON blobs. Fix is trimming templates, caching, and keeping revisions under control.
- Security plugins: heavy scanning, WAF rules, frequent writes to options. Fix is configuration, scheduling, and ensuring caches aren’t stored in autoload.
- Unbounded transients: “temporary” data that becomes permanent. Fix is cleanup and correct expiration behavior.
A practical approach: stabilize, then isolate
In production, you don’t start by uninstalling half the plugins. You start by making the system stable enough to debug.
- Raise the effective memory limit just enough to stop the crash.
- Reduce concurrency if the host is at risk of OOM.
- Reproduce the failing action on staging with the same data size.
- Disable suspected plugins one at a time, measuring memory and request time.
- Fix the root cause or replace the plugin. “But we paid for it” is not a performance strategy.
OPcache and “memory exhausted” lookalikes
Sometimes users report “memory exhausted” but what you see is 502s, slow admin, and log messages about OPcache. Different symptom, same class: memory pressure.
OPcache failure mode you should recognize
- After many deployments or plugin updates, OPcache becomes fragmented.
- New PHP files fail to cache, performance drops, and worker CPU rises.
- Reloading FPM “fixes” it temporarily because it resets OPcache.
OPcache is not optional on busy WordPress sites. But it needs enough memory and file slots to cache your codebase. A modern WordPress with WooCommerce and a few dozen plugins can easily blow through conservative defaults.
Three corporate mini-stories from the trenches
1) The incident caused by a wrong assumption
They’d just migrated a portfolio of WordPress sites from an old shared host to a shiny new VM. The team did the obvious: set define('WP_MEMORY_LIMIT','512M'); in wp-config.php across the board. They also wrote a quick internal note: “Memory issues solved.”
Two weeks later, during a scheduled plugin update window, half the sites started returning 502 errors. The on-call saw no “Allowed memory size exhausted” message in the browser and went chasing Nginx timeouts, upstream keepalive settings, and DNS. It was a full tour of everything except the problem.
The root cause was banal: PHP-FPM pools were configured with php_admin_value[memory_limit] = 128M, inherited from a hardening baseline. WordPress couldn’t override it. Admin update requests were exhausting memory and crashing workers. Nginx reported 502 because the upstream died mid-request.
The fix took ten minutes once they looked in the right place: raise the pool limit to 256M for the affected sites, reload FPM, and re-run updates. The real lesson took longer: never treat WordPress constants as the source of truth. They’re a request; the platform is the decider.
2) The optimization that backfired
An engineering manager wanted better throughput during a product launch. The team increased pm.max_children aggressively. More workers means more parallel requests, right? The CPU was fine in tests, and the site felt snappier on the homepage. Victory, for about a day.
Then the marketing email went out. Checkout traffic spiked, and so did memory. Some WooCommerce endpoints were peaking at a few hundred megabytes per request under real carts and real plugins. With the higher child count, the node ran out of RAM quickly. The kernel started killing workers. Requests retried. Load increased. The system spiraled into a self-inflicted denial of service.
They rolled back pm.max_children and raised PHP memory_limit “to be safe.” That helped a bit, but only because it reduced the frequency of PHP fatals while keeping the node in the danger zone.
The eventual stable configuration was boring: measure peak RSS per worker, set pm.max_children based on the host budget, and cap memory_limit so a single runaway request can’t take the whole node. They also moved heavy report generation off synchronous admin requests into queued jobs.
3) The boring but correct practice that saved the day
A different company ran WordPress as part of a larger platform. Nothing glamorous: separate pools per site, conservative defaults, and a change process that required one thing before tuning—measurement.
They had a small script that logged memory high-water marks for slow requests and correlated them with endpoint paths. It wasn’t a fancy APM. It was enough. When a new plugin version started consuming more memory on wp-admin/admin-ajax.php, the team spotted the shift within hours.
Instead of bumping every site to 512M, they increased memory only for the affected pool, reduced its concurrency, and opened a ticket to the plugin vendor with a reproducible dataset. Meanwhile, the rest of the fleet stayed stable and cheap.
On launch day, when traffic doubled, they didn’t discover new memory behavior. They already had the envelope. The site stayed up. Nobody wrote a heroic postmortem, which is the highest compliment in operations.
Common mistakes: symptom → root cause → fix
1) “I set WP_MEMORY_LIMIT to 512M but it still dies at 128M”
Symptom: Fatal says 134,217,728 bytes exhausted (128M) despite WordPress config.
Root cause: PHP-FPM pool uses php_admin_value[memory_limit] or global php.ini is lower; runtime changes are blocked.
Fix: Raise memory_limit in the controlling layer (FPM pool/php.ini/host UI), reload FPM, confirm via logs.
2) “We get 502/504, no PHP fatal”
Symptom: Nginx reports upstream errors; PHP logs are quiet.
Root cause: Worker killed by OS OOM, FPM crashed, or upstream timed out. Not a PHP memory_limit event.
Fix: Check dmesg for OOM kills, check FPM logs, reduce pm.max_children, align container limits, and fix high-memory endpoints.
3) “Only wp-admin fails, front-end is fine”
Symptom: Admin updates/imports/reports die; visitors can browse.
Root cause: Admin tasks use bigger datasets and may request WP_MAX_MEMORY_LIMIT, but PHP pool limit is too low or admin path triggers heavy plugins.
Fix: Set a higher admin memory budget if justified, and optimize the specific admin workflow (batching, pagination, exports).
4) “It started after enabling a security plugin”
Symptom: Random memory fatals, slow requests, big options table.
Root cause: WAF/scanning features increase per-request overhead; options/transients stored poorly.
Fix: Tune plugin settings, schedule scans off-peak, move caches out of autoload, consider object cache.
5) “Raising memory fixed it… until the next day”
Symptom: Recurring incidents with rising baseline memory, or new pages triggering it.
Root cause: Leak-like behavior (unbounded arrays, huge autoload options, runaway cron tasks) or growing dataset size.
Fix: Identify the endpoint, profile memory, fix data growth (autoload cleanup), and put guardrails on imports/reports.
6) “WP-CLI works, the website still crashes”
Symptom: CLI commands succeed; web requests fail.
Root cause: Different php.ini and memory_limit between CLI and FPM.
Fix: Tune FPM pool/php.ini for web; treat CLI as separate runtime.
Second short joke, then back to work: Setting memory_limit to 2G without measuring is like fixing a leaky pipe by buying a bigger basement.
Checklists / step-by-step plan
Emergency recovery (get the site back without making it worse)
- Confirm the failure mode. PHP fatal vs OOMKilled vs timeout.
- Raise the effective limit in the enforcing layer. For FPM: pool config. For containers: cgroup plus PHP.
- Reload, don’t restart, when possible. Reduce customer impact.
- Reduce concurrency if you’re near host limits. Lower
pm.max_childrento avoid full-node OOM. - Capture evidence. Log line with bytes exhausted, endpoint, and plugin context.
Stabilization (make it predictable)
- Measure worker RSS distribution. Use
psto get real high-water marks. - Size FPM pool. Set
pm.max_childrenbased on memory, not hope. - Set sane memory_limit. Typical production values: 256M–512M depending on workload, but only after measuring.
- Audit autoload options. Reduce baseline memory inflation.
- Check OPcache sizing. Avoid code cache starvation.
Root cause fixes (where you actually win)
- Pinpoint the endpoint. Admin-ajax? Reports? Import? Product search?
- Test disabling suspected plugins. One at a time, with controlled rollback.
- Optimize data access. Add pagination, cap export sizes, avoid loading entire result sets.
- Move heavy work async. Background jobs instead of synchronous admin requests.
- Revisit hosting constraints. If you’re on shared hosting and hitting invisible caps, plan a move.
FAQ
1) Is “Allowed memory size exhausted” always fixed by raising memory_limit?
No. It’s fixed by ensuring the request uses less memory than the limit. Raising the limit can be a valid stopgap, but leaks and unbounded workloads need code/data fixes.
2) What’s a reasonable memory_limit for WordPress in 2025?
256M is a common baseline for non-ecommerce sites. 384M–512M is common for WooCommerce and heavy admin/reporting. If you need 1G routinely, treat that as a symptom and investigate.
3) Why does wp-config.php not work sometimes?
Because PHP-FPM (or the host) can enforce memory_limit with php_admin_value or provider policy. WordPress can only request increases if PHP allows it.
4) Why does the fatal show bytes instead of megabytes?
PHP reports memory in bytes. 128M is 134,217,728 bytes; 256M is 268,435,456 bytes; 512M is 536,870,912 bytes. Use that to identify the real ceiling.
5) The site only fails during imports. Should I raise memory permanently?
Prefer to run imports in smaller batches or via a CLI tool with a temporary higher limit. Permanent high limits increase the blast radius of bugs and traffic spikes.
6) How do I tell whether the container limit is the real problem?
If you see OOMKilled events, container restarts, or kernel OOM logs without PHP fatals, you’re hitting the cgroup/OS limit. Align container memory, PHP memory_limit, and FPM concurrency.
7) Can OPcache cause “memory exhausted” errors?
OPcache memory exhaustion is separate from PHP request memory_limit, but it can cause instability and performance collapse that looks like random failures. Size OPcache to your plugin/theme footprint.
8) Should I set WP_MAX_MEMORY_LIMIT higher than WP_MEMORY_LIMIT?
Yes, often. Front-end requests should be constrained; admin tasks legitimately need more headroom. But it only works if PHP allows it and your host has the RAM for it.
9) What if I’m on shared hosting and can’t change memory_limit?
Then your real limit is your plan. Optimize the workload (disable heavy plugins, reduce autoload bloat, batch imports), and plan an upgrade or migration if the business depends on it.
10) Is raising pm.max_children a good fix for memory errors?
Usually the opposite. More children increases total memory consumption. Increase it only when you’ve measured per-worker RSS and you have spare RAM budget.
Next steps you can do today
- Decide which failure you have: PHP memory_limit fatal vs OS/container OOM vs timeout.
- Find the enforcing layer: PHP-FPM pool, php.ini, container limit, or hosting cap.
- Raise the right limit, minimally: enough to restore service, not enough to hide a leak for months.
- Do the concurrency math: per-worker RSS × max_children must fit the box with room to breathe.
- Hunt the real culprit: heavy plugins, autoload bloat, unbounded reports/imports, or cron behavior.
If you do nothing else, do this: stop treating WordPress memory settings as authoritative. The platform decides. Your job is to make that decision explicit, measured, and safe.