Allowed memory size of 134217728 bytes exhausted
PHP hit memory_limit. In Laravel it is usually a query loading too many models, a missing eager load or a growing queue worker. How to find and fix it.
PHP Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 20480 bytes)
The PHP process used all the memory memory_limit allows and was stopped. The first number is the limit in bytes; the second is the allocation that didn't fit, and it says nothing about the cause. Raising the limit makes the error go away until the data grows again, so first find what fills the memory.
| Bytes in the message | memory_limit |
|---|---|
| 134217728 | 128M, PHP's default |
| 268435456 | 256M |
| 536870912 | 512M |
| 1073741824 | 1G |
Find which limit applies
The CLI and PHP-FPM read different php.ini files, so a command can have a different limit from your web requests. Check both:
php -i | grep -E "Loaded Configuration File|^memory_limit"
php -r "echo ini_get('memory_limit'), PHP_EOL;"
For FPM, check the pool and its php.ini (on Debian and Ubuntu: /etc/php/8.3/fpm/php.ini and /etc/php/8.3/fpm/pool.d/www.conf, where php_admin_value[memory_limit] overrides it), or log ini_get('memory_limit') from a route. Restart PHP-FPM after a change.
Composer is its own case: it raises its limit to 1.5 GB and can still run out on large dependency trees. Run it with COMPOSER_MEMORY_LIMIT=-1 composer update.
A query loads every row into memory
The most common cause in Laravel. Order::all() or ->get() builds every model at once; 200,000 orders with their attributes can be gigabytes. It works in development with 50 rows and dies in production.
Process large sets in pieces instead:
// One query per 1000 rows, one model in your loop at a time
Order::where('status', 'paid')->lazy()->each(function (Order $order) {
$order->sendReceipt();
});
// When you update the column you filter on, page by id so no rows are skipped
Order::where('exported', false)->chunkById(500, function ($orders) {
foreach ($orders as $order) {
$order->update(['exported' => true]);
}
});
cursor() runs one query and builds one model at a time, but MySQL's PDO driver still buffers the full result set in memory, and it can't eager load relations. lazy() is the safer default. For a count or a sum, let the database do it: Order::where('status', 'paid')->sum('total') instead of ->get()->sum('total').
Relations load one by one, or all at once
A loop that touches a relation on each model runs one query per model (the N+1 problem), and every loaded relation stays in memory with its parent. With ->with() on a huge set, you load the parents and all their children in one go.
// Loads every order line of every order before the loop starts
Order::with('lines')->get();
// Loads lines per chunk of 500 orders
Order::with('lines')->chunkById(500, function ($orders) {
foreach ($orders as $order) {
// ...
}
});
To find lazy loading in development, add Model::preventLazyLoading(! app()->isProduction()); to AppServiceProvider::boot(). It throws on every relation that wasn't eager loaded.
A queue worker grows until it dies
A worker is one PHP process that handles job after job. Static caches, singletons that collect data, event listeners registered per job and big arrays in a service class survive between jobs, so memory creeps up over hours.
queue:work checks its memory after each job and exits when it's above --memory (128 MB by default), so the process manager starts a fresh one:
php artisan queue:work redis --memory=256 --max-jobs=500 --max-time=3600
That only helps between jobs. A single job that needs more than PHP's memory_limit still fails with this error; fix the job with the chunking above, or split it into smaller jobs. Keep --memory well below memory_limit, so the worker restarts before PHP kills it. On Laravel 13, --memory also takes a percentage of memory_limit, such as --memory=80%.
The same applies to Octane and to Artisan commands that run for hours: anything that keeps a reference to every model it has seen grows without limit.
Exports, images and files
Building an Excel export, a PDF or a ZIP in memory, or resizing a large image with GD, can take many times the file's size. Stream downloads with response()->streamDownload(), write exports in chunks (Laravel Excel's FromQuery with chunking instead of FromCollection), and resize images in a queued job with its own limit.
Raise the limit where it belongs
Once you know a job really needs more, raise the limit for that one place, not for the whole server:
public function handle(): void
{
ini_set('memory_limit', '512M');
// ...
}
memory_limit = -1 removes the limit, which turns a clean error into a server that swaps or an OOM-killed process. Don't use it in production.
Catch it in production
Laravel reports a memory error at shutdown as Symfony\Component\ErrorHandler\Error\FatalError; we confirmed it lands in the log with the same message. Laravel frees a small memory reserve at shutdown to report it, and the Laravel package sends that error to Lampwick straight away, so it opens an issue in Issues, one per file and line where memory ran out, with the request or job it broke. Before it gets that far, each request and job timeline in Lampwick shows its Peak memory, so you can see which job class creeps toward the limit. A job that kills its worker this way isn't marked failed right away: it's handed out again after retry_after and ends with MaxAttemptsExceededException, as why Laravel queued jobs fail explains. For a connection that drops in a long-running worker, see MySQL server has gone away.