Laravel Octane: Worker Lifecycle &amp; State Leakage | Mohamed Said       [Skip to content](#main)  [ ![](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MH.png) Mohamed SaidLaravel Backend Engineer ](https://www.msaied.com/public) - [Home](https://www.msaied.com/public)
- [Projects](https://www.msaied.com/public/projects)
- [Articles](https://www.msaied.com/public/articles)
- [Certificates](https://www.msaied.com/public/certificates)
- [About](https://www.msaied.com/public#about)

           [  Contact](https://www.msaied.com/public#contact) Menu 

Menu
----

Close 

 - [HomeStart here](https://www.msaied.com/public)
- [ProjectsCase studies](https://www.msaied.com/public/projects)
- [ArticlesEngineering notes](https://www.msaied.com/public/articles)
- [CertificatesCredentials](https://www.msaied.com/public/certificates)
- [AboutHow I work](https://www.msaied.com/public#about)
- [ContactGet in touch](https://www.msaied.com/public#contact)

  [Start a conversation](https://www.msaied.com/public#contact) [WhatsApp](https://wa.me/201094619204) [Email](mailto:hello@msaied.com) 

 1. [Home](https://www.msaied.com/public)
2. /
3. [Articles](https://www.msaied.com/public/articles)
4. /
5. Octane Worker Lifecycle, State Leakage, and Memory Management in Production

 Octane Worker Lifecycle, State Leakage, and Memory Management in Production
============================================================================

 Laravel Octane keeps workers alive across requests, which means static state, resolved singletons, and stale bindings can bleed between users. Here is how to find leaks and fix them before they hit production.

 ![](https://cdn.msaied.com/01M22N44A70A5MC2S599JP0MPH.webp) [Mohamed Said](https://www.msaied.com/public#person) Published 19 Jun 2026 · Updated 19 Jun 2026 · 4 min read

ShareCopy linkCopied

 ![Octane Worker Lifecycle, State Leakage, and Memory Management in Production](https://cdn.msaied.com/244/7a3a8f7b0761bfb73c6baae5f893de95.png) 

  On this page +1. [Why Octane Changes Everything About Application State](#why-octane-changes-everything-about-application-state)
2. [The Worker Request Lifecycle](#the-worker-request-lifecycle)
3. [Detecting Leaks](#detecting-leaks)
4. [Fixing Leaks: Scoped Bindings](#fixing-leaks-scoped-bindings)
5. [Resetting Static State with octane:flush](#resetting-static-state-with-codeoctaneflushcode)
6. [Memory Management: --max-requests Is Not Optional](#memory-management-code-max-requestscode-is-not-optional)
7. [Practical Checklist](#practical-checklist)
8. [Takeaways](#takeaways)

 Why Octane Changes Everything About Application State
-----------------------------------------------------

In a traditional PHP-FPM setup every request boots a fresh process. Globals, static properties, and resolved service-container bindings are destroyed when the response is sent. Octane breaks that contract: a single worker process handles thousands of requests, so anything you store in a static property or register as a true singleton persists across all of them.

This is the source of the most insidious Octane bugs — user A's authenticated model leaking into user B's request, a cached database connection holding a stale transaction, or a counter that keeps incrementing across requests.

The Worker Request Lifecycle
----------------------------

Octane wraps each request in a sandbox. Before dispatching it calls `$app->resetScope()`, which re-resolves bindings tagged as `scoped`. After the response is sent it fires `RequestHandled` and runs any registered termination callbacks.

The key distinction:

- **Singleton** (`app()->singleton(...)`) — resolved once per worker, never reset.
- **Scoped** (`app()->scoped(...)`) — resolved once per request, flushed after it.
- **Bind** (`app()->bind(...)`) — resolved fresh on every `make()` call.

If you register something as a singleton that holds per-request state (e.g. the currently authenticated user, a request-specific logger context, a DTO built from `Request`), you have a leak.

Detecting Leaks
---------------

The fastest way to reproduce a leak locally:

```bash
php artisan octane:start --workers=1 --max-requests=0

```

With a single worker and no request cap, fire two requests with different authenticated users and dump the resolved singleton between them.

```php
// routes/web.php
Route::get('/debug-singleton', function (MyStatefulService $svc) {
    return $svc->userId(); // should differ per request
});

```

If both requests return the same user ID, `MyStatefulService` is a leaking singleton.

Fixing Leaks: Scoped Bindings
-----------------------------

The cleanest fix is to change `singleton` to `scoped` in your service provider:

```php
// Before — leaks across requests
$this->app->singleton(CurrentTenantResolver::class, function ($app) {
    return new CurrentTenantResolver($app->make(Request::class));
});

// After — Octane flushes this after every request
$this->app->scoped(CurrentTenantResolver::class, function ($app) {
    return new CurrentTenantResolver($app->make(Request::class));
});

```

Octane calls `$app->forgetScopedInstances()` at the end of each request cycle, so the next `make()` call gets a fresh instance.

Resetting Static State with `octane:flush`
------------------------------------------

For third-party packages that use static properties internally, register a flush callback:

```php
use Laravel\Octane\Facades\Octane;

Octane::tick('flush-static-cache', function () {
    SomePackage::resetStaticCache();
})->seconds(0); // runs after every request via the 'RequestHandled' event

```

Alternatively, listen to the Octane request lifecycle events directly:

```php
use Laravel\Octane\Events\RequestHandled;

Event::listen(RequestHandled::class, function () {
    MyStaticRegistry::flush();
});

```

Memory Management: `--max-requests` Is Not Optional
---------------------------------------------------

Even with perfect scoping, memory grows. PHP's garbage collector does not always reclaim cyclic references inside long-lived closures. Set a sane `--max-requests` limit:

```ini
# octane config or supervisor
max_requests=500

```

This tells Octane to gracefully restart the worker after 500 requests. Combine it with Supervisor's `autorestart=true` so the slot is immediately refilled. Monitor worker RSS with:

```bash
watch -n2 'ps aux | grep octane'

```

If RSS climbs linearly and does not plateau, you have a reference cycle or a collection that is never cleared.

### Practical Checklist

- Audit every `singleton` binding that touches `Request`, `Auth`, or tenant context — convert to `scoped`.
- Never store request-derived state in a static property without a flush callback.
- Use `--max-requests` in production; tune it based on observed memory growth.
- Run `php artisan octane:start --workers=1` locally and replay the same route twice with different inputs to catch leaks early.
- Add a `RequestHandled` listener in staging that dumps memory usage per worker.

Takeaways
---------

- Octane's persistent worker model means singletons live for the worker's entire lifetime, not just one request.
- Use `scoped` bindings for anything that depends on per-request context.
- Register `RequestHandled` listeners or `Octane::tick` callbacks to flush third-party static state.
- `--max-requests` is a safety valve, not a substitute for correct scoping.
- A single-worker local setup is the fastest way to reproduce and verify leaks before they reach production.

- [laravel](https://www.msaied.com/public/articles?search=laravel)
- [octane](https://www.msaied.com/public/articles?search=octane)
- [performance](https://www.msaied.com/public/articles?search=performance)
- [swoole](https://www.msaied.com/public/articles?search=swoole)
- [roadrunner](https://www.msaied.com/public/articles?search=roadrunner)

 Frequently asked questions 
---------------------------

  What is the difference between `singleton` and `scoped` in Laravel Octane?A `singleton` is resolved once per worker process and persists across all requests that worker handles. A `scoped` binding is also resolved once, but Octane flushes it after each request via `forgetScopedInstances()`, so the next request gets a fresh instance.

   How do I safely use a package that stores state in static properties under Octane?Listen to the `Laravel\\Octane\\Events\\RequestHandled` event and call the package's reset or flush method inside the listener. If the package has no reset method, consider wrapping it in a scoped service that re-instantiates it per request.

   Should I set `--max-requests` even if I have no obvious memory leaks?Yes. PHP's garbage collector does not always reclaim cyclic references in long-lived closures. A bounded `--max-requests` value (e.g. 500–1000) provides a safety net and keeps worker RSS predictable in production.

   ![Mohamed Said](https://cdn.msaied.com/01M22N44A70A5MC2S599JP0MPH.webp)About the author
----------------

[Mohamed Said](https://www.msaied.com/public#person)Senior Backend Engineer specializing in Laravel, scalable SaaS platforms, APIs, and cloud infrastructure. I build secure, high-performance web applications that help businesses grow.

[About](https://www.msaied.com/public#about) [GitHub ↗](https://github.com/EG-Mohamed) [LinkedIn ↗](https://www.linkedin.com/in/msaiedm/) [WhatsApp ↗](https://wa.me/201094619204) [Email Address ↗](mailto:hello@msaied.com) [My CV ↗](https://drive.google.com/file/u/0/d/1MF20IPRJyzfy32mhEutjL5EpSls0w2Q8/view)  

   [Previous articleTesting Filament Resources, Actions, and Form Assertions with Pest](https://www.msaied.com/public/articles/testing-filament-resources-actions-and-form-assertions-with-pest) [Next articleFilament v3 Custom Field Plugins, Custom Columns, and Render Hooks](https://www.msaied.com/public/articles/filament-v3-custom-field-plugins-custom-columns-and-render-hooks)  

   On this page
-------------

1. [Why Octane Changes Everything About Application State](#why-octane-changes-everything-about-application-state)
2. [The Worker Request Lifecycle](#the-worker-request-lifecycle)
3. [Detecting Leaks](#detecting-leaks)
4. [Fixing Leaks: Scoped Bindings](#fixing-leaks-scoped-bindings)
5. [Resetting Static State with octane:flush](#resetting-static-state-with-codeoctaneflushcode)
6. [Memory Management: --max-requests Is Not Optional](#memory-management-code-max-requestscode-is-not-optional)
7. [Practical Checklist](#practical-checklist)
8. [Takeaways](#takeaways)

 ###  Have a technical challenge?

 Tell me what you’re building. I reply within two working days.

[Start a conversation](https://www.msaied.com/public#contact) 

   Related articles
-----------------

 [ ![](https://cdn.msaied.com/740/cce86edc21eddcbdd2f2454fadaf9c70.png)  · 3 min read### The Pipeline Pattern in Laravel: Custom Pipelines Beyond Middleware

5 Oct 2026 ](https://www.msaied.com/public/articles/the-pipeline-pattern-in-laravel-custom-pipelines-beyond-middleware-1) [ ![](https://cdn.msaied.com/739/2d6897fdcdcf090613f96f72a64b8a78.png)  · 4 min read### MySQL Full-Text Search in Laravel: Indexes, Relevance Scoring, and Boolean Mode

4 Oct 2026 ](https://www.msaied.com/public/articles/mysql-full-text-search-in-laravel-indexes-relevance-scoring-and-boolean-mode) [ ![](https://cdn.msaied.com/738/073696a3fefe18bec825beec5ac658f5.png)  · 4 min read### Laravel Queue Rate-Limited Middleware: Throttling Jobs Without Losing Work

4 Oct 2026 ](https://www.msaied.com/public/articles/laravel-queue-rate-limited-middleware-throttling-jobs-without-losing-work) 

  Have a technical challenge?
----------------------------

Tell me what you’re building. I reply within two working days.

 [Discuss your project ↗](https://www.msaied.com/public#contact) 

  © 2026 Mohamed Said · Built with Laravel, meant to last.Senior Backend Engineer specializing in Laravel, scalable SaaS platforms, APIs, and cloud infrastructure. I build secure, high-performance web applications that help businesses grow.

 - [Home](https://www.msaied.com/public)
- [Articles](https://www.msaied.com/public/articles)
- [Certificates](https://www.msaied.com/public/certificates)
- [GitHub](https://github.com/EG-Mohamed)
- [LinkedIn](https://www.linkedin.com/in/msaiedm/)
- [WhatsApp](https://wa.me/201094619204)
- [Email Address](mailto:hello@msaied.com)
- [My CV](https://drive.google.com/file/u/0/d/1MF20IPRJyzfy32mhEutjL5EpSls0w2Q8/view)
- [Sitemap](https://www.msaied.com/public/sitemap.xml)
