# 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.

```text
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:

```bash
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:

```php
// 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.

```php
// 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:

```bash
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:

```php
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](/docs/laravel/) 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](/guides/laravel-failed-jobs/) explains. For a connection that drops in a long-running worker, see [MySQL server has gone away](/errors/mysql-server-has-gone-away/).
