Scaling Laravel with Octane: Managing State in a Persistent World
Laravel Octane boosts performance by keeping your app in memory, but introduces risks like state leakage and DB connection exhaustion. Learn how to manage persistent workers safely.
25 Mar 2026, 22:25 UTC

The Bootstrapping Bottleneck
In a traditional PHP-FPM environment, every single HTTP request starts from zero. Laravel must load the framework, register service providers, and boot the container before a single line of your controller logic executes. This adds a consistent overhead of 50-100ms per request.
Laravel Octane solves this by booting your application once and keeping it in memory. Subsequent requests are handed to a "warm" worker process, eliminating the bootstrap cost. While this can increase throughput by 2x to 50x, it fundamentally changes the PHP lifecycle: your application is no longer short-lived; it is a long-running process.
Choosing Your Engine: Swoole vs. RoadRunner
Octane acts as a bridge to two high-performance servers, each with a different operational model:
- Swoole: A PHP extension written in C. It uses a coroutine-based model, allowing a single worker to handle thousands of concurrent I/O-bound connections asynchronously. It offers the highest raw performance but requires a Linux/macOS environment and a compiled extension.
- RoadRunner: A Go-based binary that manages a pool of PHP workers. It follows a more traditional worker-per-request model, making it easier to deploy and compatible with Windows. It is generally more stable for teams moving from FPM who aren't ready for coroutine-based concurrency.
The Danger of State Leakage
Because the application stays in memory, any data stored in a static property or a singleton service persists across different users' requests. This is known as state leakage. If you inject the current Request object into a singleton's constructor, that singleton will hold onto the first user's request forever, serving their data to every subsequent visitor.
Identifying Leaky Services
Avoid using the request() helper or Auth::user() inside the __construct method of a singleton. Instead, resolve these dependencies dynamically within the method where they are needed, or use a closure to resolve them from the container at runtime.
Worked Example: Resetting Service State
If you use a third-party package or a custom service that maintains internal state (like a breadcrumb collector or a temporary calculation cache), you must reset it between requests. Octane provides a mechanism to flush state via the octane.php configuration or by listening to events.
To implement a manual reset for a custom CartService singleton, add the service to the flush array in config/octane.php:
// config/octane.php
return [
'flush' => [
App\Services\CartService::class,
// Other services that need to be reset
],
];
Alternatively, if the service requires a specific method call to clear data, you can use the Octane::terminate method in your AppServiceProvider:
// app/Providers/AppServiceProvider.php
public function boot()
{
\Laravel\Octane\Facades\Octane::terminate(function ($app, $request, $response) {
$app->make(\App\Services\CartService::class)->clearInternalCache();
});
}
Database Connection Scaling
In PHP-FPM, connections are closed after the request. In Octane, workers maintain persistent connections. This can lead to Too many connections errors on your database server.
The math is simple: Total Connections = (Number of Workers) × (Pool Size per Worker). If you deploy 16 workers across 3 server nodes with a pool size of 10, you are occupying 480 connections regardless of actual traffic. You must increase your database's max_connections setting to accommodate this baseline.
Limitations and Verification
Octane is not a "drop-in" performance boost for every app. Some legacy Redis clients or non-coroutine-safe libraries will block the entire Swoole worker, negating the benefits of async I/O.
How to verify stability:
- Memory Growth: Run the server and monitor the Resident Set Size (RSS) using
ps aux | grep octane. If memory climbs linearly without plateauing, you have a leak. - State Isolation: Create a test endpoint that increments a static counter. Hit it 10 times. If the counter returns 10 instead of 1, your state is persisting (which may be a bug or a feature depending on the use case).
- Load Testing: Use a tool like
wrkto ensure the server handles concurrent requests without dropping connections:wrk -t4 -c100 -d30s http://localhost:8000
Rollback: To revert to standard behavior, stop the Octane server and restart your PHP-FPM/Nginx stack. No code changes are required unless you modified service providers specifically for Octane.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.