Skip to content
← Back to Blog

How to Fix PHP "Allowed Memory Size Exhausted" Errors in Laravel and WordPress

Fix "PHP Fatal error: Allowed memory size exhausted" in Laravel and WordPress: correct memory_limit config, chunking, cursors, and common leak fixes — with working code.

Edited 1 day ago
How to Fix PHP "Allowed Memory Size Exhausted" Errors in Laravel and WordPress

How to Fix PHP "Allowed Memory Size Exhausted" Errors in Laravel and WordPress

If you ship PHP for a living, you will meet this fatal error sooner or later:

BASH
PHP Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 4096 bytes) in /var/www/app/Services/ReportBuilder.php on line 88

In Laravel, the "allowed memory size exhausted" error usually hides inside storage/logs/laravel.log while your users just see a 500 page. In WordPress it is a white screen of death, or a 500 on admin-ajax during a backup, import, or page-builder save. In Composer it kills dependency installs mid-resolution. The error means one PHP process asked for more RAM than its memory_limit allows — and the fix is almost never just "raise the number and hope". Here is the method I use on client projects to kill the "allowed memory size exhausted" error for good: find the real limit, raise it in the right place for your runtime, then fix the code that blows through it.

What "Allowed Memory Size Exhausted" Actually Means

PHP gives every single process its own memory ceiling, set by the memory_limit directive. The default is 128M. When a script's allocations cross that line, PHP kills it immediately with a fatal error — there is no catching it, no retry, no graceful fallback. The "tried to allocate X bytes" part tells you how much the script wanted at the moment of death, and the file/line points at the allocation that pushed it over — which is often not the real culprit, just the last straw.

Two important truths shape every fix below.

First, PHP has several different runtimes on one machine, and each reads its own config. Your web app (PHP-FPM) and your command line (CLI) often use completely separate php.ini files. Raising the limit for the web SAPI does nothing for a Composer install or an Artisan command, and vice versa. Most "I raised it but it still fails" stories come from editing the wrong file.

Second, raising the limit treats the symptom. Legitimate workloads — big imports, report generation, image pipelines — sometimes do need more headroom. But an unbounded query, an infinite recursion, or a queue worker leaking a little on every job will eat any limit you give it. Give a leak 1GB and it dies at 1GB instead of 128MB, at 3 a.m. instead of 2 p.m. The durable fix is almost always in the code.

Step 1: Find the Limit You Are Actually Hitting

Before changing anything, confirm the current limit for the runtime that failed:

BASH
php -i | grep -i "memory_limit"

For a web request, drop this into a scratch route or script (never leave it on production):

PHP
<?phpecho 'memory_limit = ' . ini_get('memory_limit') . PHP_EOL;echo 'peak so far = ' . round(memory_get_peak_usage(true) / 1024 / 1024, 2) . ' MB' . PHP_EOL;

If php -i says 512M but your Laravel queue job still dies at 128M, you are probably reading the CLI config while the worker runs under supervisor with a different php.ini, or the web config while the failure is on CLI. Run php --ini to see exactly which files the CLI loads, and use phpinfo() or a route for the web side. Also check for overrides: ini_set() calls in bootstrap files, .htaccess php_value lines, and host panels (cPanel MultiPHP INI Editor, Plesk, RunCloud) can all beat php.ini in different ways.

In Laravel, start in storage/logs/laravel.log — the same log you would check for database connection failures (my walkthrough of the SQLSTATE[HY000] [2002] "Connection refused" error lives at https://webixyhub.com/blog/fix-sqlstate-hy000-2002-connection-refused-laravel). Confirm the exact byte count and the file/line before touching any config.

Step 2: Raise the Limit in the Right Place

Once you know which runtime failed, here is how to increase the PHP memory limit in exactly that place:

  • php.ini (server-wide): memory_limit = 512M, then restart PHP-FPM or Apache. The cleanest fix when you own the server.
  • PHP-FPM pool config: php_admin_value[memory_limit] = 512M — per-site, survives php.ini resets on managed hosts.
  • Apache .htaccess: php_value memory_limit 512M — works only on mod_php, not FPM. If you get a 500 after adding it, your host blocks the directive; remove the line.
  • Single script: ini_set('memory_limit', '512M'); at the top of a CLI script or Artisan command — the safest, affects nothing else.
  • Single command: php -d memory_limit=512M artisan reports:generate — no permanent change at all.
BASH
; php.ini — pick a sane production value, not -1

memory_limit = 512M


BASH
# reload the right service afterwards

sudo systemctl restart php8.3-fpm

# or Apache:

sudo systemctl restart apache2

A note on -1 (unlimited): it is fine for a Composer install or a one-off migration on your own machine. In production it is a loaded gun — one bad query and PHP eats all server RAM until the OOM killer shoots the process (or worse, takes MySQL down with it). If your first instinct is -1 in production, keep reading: you need the next section more than a bigger number.

Step 3: WordPress — Use WP_MEMORY_LIMIT Correctly

WordPress has its own layer: WP_MEMORY_LIMIT in wp-config.php. This raises the limit for front-end requests only; admin pages use WP_MAX_MEMORY_LIMIT (default 256M), which needs its own define if you want higher:

PHP
<?php// wp-config.php — above "/* That's all, stop editing! */"define('WP_MEMORY_LIMIT', '256M');define('WP_MAX_MEMORY_LIMIT', '512M');

The critical detail most guides skip: WP_MEMORY_LIMIT can never exceed the server's memory_limit. If your host sets PHP's memory_limit to 128M, defining 512M in wp-config.php does absolutely nothing. Check both, and on shared hosting you usually have to raise PHP's limit first via the host panel or .user.ini — or accept 256M and fix the plugin instead.

The usual WordPress offenders: backup plugins on big media libraries, page builders saving huge layouts, WooCommerce with tens of thousands of products, and bulk importers. Deactivate suspects one at a time and re-run the failing action; the Query Monitor plugin will show you exactly which plugin's query or hook burns the memory.

Step 4: Composer and Artisan Have Their Own Memory Ceiling

The 1610612736-bytes error during composer install is so common it deserves its own fix. Composer runs as a CLI process with its own memory handling:

BASH
# one-off: unlimited memory for this install only

COMPOSER_MEMORY_LIMIT=-1 composer install

# or inline per-command for artisan

php -d memory_limit=1024M artisan migrate --force

Do not set memory_limit = -1 globally in the CLI php.ini just to make Composer happy — every future cron and queue worker inherits it. The per-command flags are the professional move: they fix the install and leave production boundaries intact.

Step 5: Fix the Code That Actually Leaks

This is the section that permanently solves the "allowed memory size exhausted" problem. Three patterns cover most Laravel memory exhaustion I see on client projects.

Pattern 1: unbounded queries. Model::all() or ->get() on a big table hydrates every row into an object. Ten thousand users is a slow page; a million rows is a fatal error. Use chunk() for batch updates and cursor() for streaming exports:

PHP
<?php// BAD: loads every user into memory at once$users = User::all();foreach ($users as $user) {$user->notify(new MonthlyReport());}// GOOD: constant memory no matter how big the table getsUser::chunk(500, function ($users) {foreach ($users as $user) {$user->notify(new MonthlyReport());}});


PHP
<?php// For exports and reports: cursor() streams one row at a time$handle = fopen('php://output', 'w');foreach (Order::cursor() as $order) {fputcsv($handle, [$order->id, $order->total, $order->created_at]);}fclose($handle);

chunk() supports eager loading per batch (see my walkthrough of fixing the N+1 query problem in Laravel at https://webixyhub.com/blog/fix-n1-query-problem-laravel-eager-loading); cursor() is leaner but cannot eager-load. Pick chunk() for updates with relations, cursor() for plain row streaming.

Pattern 2: fetching more columns than you need. Select only what you use:

PHP
<?php// Fetches every column, including longtext blobs you never read$posts = Post::where('status', 'published')->get();// Fetches 3 columns — often 10x less memory$posts = Post::where('status', 'published')->select('id', 'title', 'slug')->get();

Pattern 3: 

Debug and cache data that grows. Laravel Debugbar, DB::enableQueryLog() left on, and log channels that buffer stack traces all accumulate per request. In long Artisan commands, call DB::disableQueryLog(), unset() large temp variables, and avoid holding collections you already wrote out. And if you store big structures in cache or session, remember that unserializing them costs the same memory all over again on every read.

Step 6: Queue Workers and Image Processing Are the Silent Killers

Two production patterns trigger "allowed memory size exhausted" without any obvious bug.

Long-running queue workers accumulate memory across hundreds of jobs — Eloquent models, logs, and container bindings pile up. This is the same class of problem I cover when webhooks misbehave (my guide to fixing Stripe webhook signature verification failures in Laravel: https://webixyhub.com/blog/fix-stripe-webhook-signature-verification-failed-laravel). Cap workers so they restart before leaks accumulate:

BASH
# restart the worker before leaks accumulate

php artisan queue:work --memory=256 --max-jobs=500 --max-time=3600

Image processing is the other classic: GD loads the full decompressed bitmap into memory, so a 10MB JPEG can need 200MB+ of RAM. Process uploads on the queue — never in the web request — cap worker memory, and for heavy pipelines prefer the libvips driver (Intervention Image supports it), which streams instead of loading whole images.

Common Pitfalls and Their Fixes

  • "I raised memory_limit but nothing changed." You edited the wrong php.ini (CLI vs FPM), forgot to restart the service, or a .user.ini, pool config, or ini_set() is overriding you. Verify with the Step 1 snippet after the change.
  • Limit written as 512MB. PHP reads the trailing B as bytes — your limit becomes 2MB, not 512MB. Use 512M.
  • White screen in WordPress with no error. Error display is off and the log is elsewhere. Enable WP_DEBUG and WP_DEBUG_LOG in wp-config.php and read wp-content/debug.log.
  • Error only during imports or cron. Those run on CLI or a different worker with a different limit — check that runtime specifically, and consider chunking the import.
  • Error right after a plugin or theme update. Something new is allocating. Roll back or deactivate to confirm, then profile before raising limits.

The Quick Checklist

  1. Reproduce and note which runtime failed (web, CLI, cron, worker).
  2. Read the real limit for that runtime — php -i, php --ini, or ini_get().
  3. Raise it in the right place, for that runtime only.
  4. Find the allocation pattern: unbounded get(), missing chunk/cursor, image processing in the request, leaking worker.
  5. Fix the pattern; keep the raised limit as headroom, not as the fix.
  6. Monitor peak usage (memory_get_peak_usage(true) in logs or your APM) so the next regression shows up before users do.

More Articles

Laravel How to Fix 429 Too Many Requests in Laravel: Throttle and Rate Limiting Done Right Python How to Hire a Python Developer in 2026 Without Hiring the Wrong One Laravel How to Fix the N+1 Query Problem in Laravel (Eager Loading Explained)