# How do I debug memory leaks under Laravel Octane (FrankenPHP)?

> Plot the memory_get_usage curve first, bisect service providers to narrow the culprit, then reset stateful singletons in Octane's hooks and clear statics.

- Asked: 2026-05-13
- Answered: 2026-05-16
- Asked by: Yiğit
- Tags: performans, laravel, octane
- Source: https://muhammetsafak.com/just-ask/debugging-memory-leaks-under-laravel-octane-frankenphp/
- Language: en-US
- Author: Muhammet Şafak

---
**Question:** We run Laravel Octane with the FrankenPHP driver at `max-requests=500`. Under heavy traffic the workers' memory climbs fast, RAM balloons, and the server gets close to locking up.

PHP-FPM clears memory after every request, but Octane is stateful — singletons, static variables, and third-party packages cause this leak. How do I debug it, and how do I optimize memory while keeping workers persistent?


Short answer: don't guess, **measure** — in Octane the worker is long-lived, so anything that accumulates between requests leaks; track what's piling up first, then reset the stateful side in the hooks.

## Short answer

The real issue: PHP-FPM assumes "a fresh process per request," but Octane keeps the worker alive. Everything that breaks that assumption — growing static arrays, singletons holding request state, listeners/bindings registered per request, and any state Octane doesn't know about — quietly inflates memory. Octane resets its own framework state between requests (auth guards, log context, the request instance); the state it can't see is yours to reset. I covered what the persistent-process model changes in the [Laravel Octane post](/blog/laravel-octane-persistent-process-performance/).

## Why

1. **A leak here isn't a bug, it's a broken assumption.** Under PHP-FPM every request was born and died in its own process, and leaky code died with it. Octane removes that safety net and the same code starts accumulating.

2. **The culprit usually isn't your code.** A third-party package that assumes a fresh process per request is invisible under PHP-FPM and explosive under Octane. That's why this needs measurement, not reading.

3. **`--max-requests` hides the root cause.** Recycling the worker after a set number of requests caps RAM but doesn't close the leak — lowering `500` just moves you from crashing fast to crashing late.

## What to do

1. **Measure first, talk later.** Log `memory_get_usage(true)` every N requests; if memory climbs monotonically you have a leak, if it oscillates and drops it's normal. Drop a counter into Octane's `RequestTerminated` hook and look at the shape of the curve.

2. **Bisect to find the culprit.** Disable service providers and suspect packages one at a time and re-measure; wherever the growth stops, that's your leak. A snapshot diff (the delta of `memory_get_usage` between two points) narrows which type is accumulating.

3. **Reset stateful singletons in the hooks.** Clear bindings that hold request state in the `RequestReceived`/`RequestTerminated` hooks, or via the `flush` list in `config/octane.php`. Leaving the singleton in the container but resetting the state inside it closes most leaks in a single line.

4. **Kill static accumulation, rebind services per request.** Ever-growing static `$cache = []` arrays, connections that never close, collections that swell per request — all classic. Don't make a request-scoped service a singleton; rebind it each request so the data it carries dies with the request.

5. **Keep `--max-requests` on as a safety valve.** But do the actual work by closing the leak; don't lean on it.

**Bottom line:** if it were me, I'd order it like this — plot the curve with `memory_get_usage` first, narrow the culprit by bisecting service providers/packages, then reset stateful singletons in Octane's hooks and clear static accumulation. Keep `--max-requests` as a net but don't lean on it. If you want Octane's speed, you have to accept "the process doesn't die per request" in every layer of your code.

## Related Reading

- [Laravel Octane: the performance that comes with a persistent process](/blog/laravel-octane-persistent-process-performance/) — Blog
- [Should I use chunkById instead of chunk when the same job also updates the rows it iterates?](https://muhammetsafak.com/just-ask/should-i-use-chunkbyid-instead-of-chunk-when-the-same-job/) — Just Ask
- [Should I enable Model::preventLazyLoading only locally or in production too?](https://muhammetsafak.com/just-ask/should-i-enable-model-preventlazyloading-only-locally-or-in-production-too/) — Just Ask
- [How do I stream 2-5 GB file downloads without blowing up PHP's memory?](https://muhammetsafak.com/just-ask/streaming-multi-gigabyte-file-downloads-without-exhausting-php-memory/) — Just Ask
