12 min read
Delivery Manager & Software Architect

We run Laravel Octane in production across a large part of our client fleet, payment platforms, tenant-based SaaS, and internal tooling, on laravel/octane v2. So this isn't a spec summary: it's what we've learned shipping it, including the one bug that cost us the most time (spoiler: it wasn't performance tuning, it was a singleton quietly serving the wrong tenant's data).
If you're deciding whether to adopt Octane, which server to run, and how not to get burned, this is the guide we wish we'd had.
Here's the fastest orientation:
static property or a singleton persists between requests. That's the source of Octane's most surprising bugs. We cover the exact fix below.Laravel Octane is a first-party package that serves your app through a high-performance application server (FrankenPHP, Swoole/Open Swoole, or RoadRunner) which boots the framework once and keeps it resident in memory, then hands incoming requests to long-lived workers.
Traditional PHP-FPM is shared-nothing: every request bootstraps the framework, wires the container, registers service providers, handles the request, then throws it all away. It's simple and crash-safe, but you pay the full bootstrap cost on every single hit.
Octane inverts that. The bootstrap happens once per worker. On subsequent requests, the same booted application instance is reused, so the register and boot methods of your service providers run once, not per request. You keep Laravel's expressive API; you drop the repeated boot tax. The trade-off is that your app now has memory that lives between requests, which is exactly what makes it fast and exactly what you have to manage.
Install it in two commands:
composer require laravel/octane
php artisan octane:install
octane:install publishes config/octane.php and prompts you to choose a server.
In 2026, FrankenPHP is the default most teams should start with (it's the server Laravel's own installer and production examples lead with, and the one we default to in our own config). Choose RoadRunner if you want the most battle-tested, process-isolated option for a classic monolith, and Swoole/Open Swoole if you need coroutine concurrency, concurrent tasks, ticks/intervals, or the Octane cache and tables - those features are Swoole-only.
| FrankenPHP | RoadRunner | Swoole / Open Swoole | |
|---|---|---|---|
| Built on | Go + Caddy, single binary | Go binary | PHP C extension |
| Install | Auto-downloaded by Octane | Auto-downloaded on first start | pecl install swoole (or openswoole) |
| Standout | HTTP/2 + HTTP/3, early hints, Brotli/Zstd, no separate web server needed | Mature, process isolation, easy to operate | Coroutines, Octane::concurrently(), ticks, Octane cache & tables |
| Best for | New apps, containers, cloud-native/autoscaled | Traditional mixed web+API monolith | Async/high-concurrency, parallel upstream calls |
| Concurrent tasks / ticks / Octane cache / tables | No | No | Yes (only here) |
Two things we've learned in production that the docs don't spell out:
config/octane.php ships with 'server' => env('OCTANE_SERVER', 'frankenphp') Fewer moving parts in the request path are worth a lot operationally.Octane::concurrently(), ticks/intervals, the 2M-ops/sec Octane cache, and Swoole tables- are genuinely powerful, but they're Swoole-only. If you're not using them, FrankenPHP or RoadRunner is simpler, and the raw request throughput difference rarely justifies Swoole's operational sharp edges. Our internal rule: write driver-agnostic code and detect the driver withconfig('octane.server'), guarding any Swoole-only call behind that check
The developers love Laravel; even so, there is to raise an issue regarding the performance. We’re not saying that Laravel is slow, but at the very least, it’s not the best beast in the field. Laravel Octane tries to cut that issue in half.
First, let’s understand how Laravel Application is traditionally served under a web server. After a web-server(Apache or Nginx) gets a request, it then delegates the request to PHP-FPM, starting a new worker or reusing the available one.
A worker spawns the new process upon executing a PHP script. After which PHP will include all the files associated with the Laravel Project, and it’s going to read them from the hard disk, and reading from hard disk is always time and resource consuming. Then PHP will bootstrap the Laravel Framework and its dependency container, which most of the time takes much more time and resources than executing business-specific logic.
And finally, our business logic is executed. The idea is that creating new processes, including all those many files from the disk, bootstrapping framework is just time and resource-consuming, and it happens on each and every request. So then, what can Laravel Octane do about it?
Laravel Octane simply caches bootstrapped laravel framework in the RAM on the first request. Every request after it just uses the already bootstrapped framework from the RAM instead of reading the files from the hard disk and re-bootstrapping the framework. And that’s the core idea that boosts the framework performance so much.
Even though Octane supercharges your Laravel application, you should consider some issues before jumping in this boat.
The idea that Octane saves a bootstrapped framework in the RAM means that Laravel Application becomes stateful. Quite simply, any runtime changes to the class instances, class static properties, and variables are preserved. And every variable and instance that you created during the request is preserved and available to each and every subsequent request.
So it is the perfect environment for memory leaks. Worst of all, we, PHP programmers, aren’t used to thinking about garbage collection and memory leaks and stuff like that because traditional applications would isolate one process for each request. After sending the response, the process would immediately die. So each request would be sandboxed in their process, and memory leaks become next to impossible as all the memory gets freed at the time of process termination.
But the Laravel team tries to reduce such traps and possibilities where memory leaks can happen. So, they’re reusing the Dependency Container assembled on the first request. Dependency Container is the core of the laravel foundation, and it’s a cornerstone of the framework. The Dependency Container takes care of auto wiring, resolving/registering/providing services, and so on. As it is the core component of the framework, Laravel Team decided that it would be better if they made it cross-request immutable. And so, this is what happens.
When the Octane server is initiated, on the first request, the framework is bootstrapped and saved in shared memory, and also the Dependency Container is saved separately. Let’s call it the Original Dependency Container. Then, on each and every subsequent request, this Original Dependency Container is cloned and provided for this request. After the request is terminated, this clone gets destroyed. And on the upcoming requests, the same scenario is played.
The idea is to always have the Original Dependency Container on every request, and if the Container is modified, it should not affect the other upcoming requests. Therefore, we can say that the Octane-powered Laravel application is partially stateful. However, as Mohamed Said, a member of the Laravel Team, noted, PHP libraries and developers aren’t ready for Laravel to become completely stateful. So, when working with Dependency Container and Service Providers, keep in mind that Octane preserves Dependency Container, and everything should be okay.
Also, keep in mind that the Octane-powered Laravel application is different from the traditional Laravel application. Octane has its server, and web servers like Nginx and Apache should only redirect incoming traffic to the Octane Server and pass additional proxy headers if necessary. Another caveat is that you need to start the Octane server in watch mode during the development process for the server to auto-restart on code changes. That’ll be very familiar if you have a little bit of Node background.
“In a post-opcache, post-octane world - I see no reason to choose Lumen for a new project,” - Says Taylor Otwell, Creator of Laravel.
Lumen is a micro-framework based on Laravel, frankly speaking, it’s a Laravel framework without some of the components to make it fast. So Laravel with Octane as its server is way more performant than Lumen. Let’s see some real numbers, shall we?
I decided to stress test the Laravel application in 3 modes:
Well, the results were as you might have imagined. For testing, I used well known, popular tool wrk, with the following configuration:
wrk -t1 -c50 URL
And the testing route was responding with just Hello World HTML, nothing fancy, but even with that, the result is quite interesting.
| Laravel Mode | Handled Request in 10 Seconds | RPS - Request per Second |
| Laravel with Octane | 2667 | 266 rps |
| Laravel with Apache | 1210 | 121 rps |
| Laravel with Built-In Server | 705 | 70 rps |
So, as it appears, Laravel with Octane as its server is at least twice as fast as traditional Apache-Served Laravel.

As I mentioned earlier, if you are to use Octane, it’s way better to go about it with the Swoole PHP extension, as it provides some additional cool features. Of course, the Swoole extension itself is a whole other story, and it should have an article of its own, but let’s, for now, dive into what we can get from Swoole with the Octane.
Concurrent Tasks - one of the cool features of Swoole is concurrency. You can do several tasks at once. It is not full-fledged thread support, but even this small addition of concurrency in PHP may come unbelievably handy. For example, as shown below in the picture, you can use available threads and make several asynchronous queries on the database. And it’s not limited to the database; you can do any work with Concurrent Tasks.
Ticks & Intervals - Traditionally, as we’ve already said many times, Laravel is not stateful, meaning - every request has its own PHP script execution, and there is no built-in server in production applications. For that reason, tasks that are to be executed in the scheduled timeline are triggered with the help of a Linux built-in cron. But with Octane, Laravel gets statefulness and its own server, and it does not need cron to trigger some actions in a scheduled manner. All this logic can be done within the Octane server. And to utilize that functionality, we need to register our scheduled tasks in the boot method of the Service Provider with the help of Octane Ticks, and Octane will take care of triggering those tasks.
The Octane Cache - And so, there come more perks with statefulness. With the ability of Swoole Tables and statefulness, we can now enjoy the new, lightning-fast cache repository - The Octane Cache. As documentation tells us, it’s capable of 2 million operations per second - reads and writes, which is just mind-blowing! The only drawback to this caching system is that it will flush whenever we restart or stop the Octane server, as it is persisted in the shared memory - RAM.
Short answer: Because Octane keeps your booted app in memory, any object bound as a singleton - or any static property - survives between requests. If that object captured a per-request thing (the container, the current request, the current user, or the current tenant), later requests get stale, wrong, or leaked data. The fix is to never let a singleton hold a per-request value directly: inject a resolver closure instead, and never accumulate into static.
This is the single most important section on the page, because it's the trap that turns "Octane made us faster" into "Octane served User A's data to User B." Here's the mechanism and the exact fixes.
Problem 1 — a singleton captures the container (and goes stale):
use App\Service;
use Illuminate\Contracts\Foundation\Application;
public function register(): void
{
// ❌ The container is captured once at boot and reused forever.
$this->app->singleton(Service::class, function (Application $app) {
return new Service($app);
});
}
Fix - inject a resolver closure that always returns the current container:
use App\Service;
use Illuminate\Container\Container;
// ✅ The closure resolves the *current* container on every use.
$this->app->singleton(Service::class, function () {
return new Service(fn () => Container::getInstance());
});
Container::getInstance() and the global app() helper always return the latest container.
Problem 2 - a singleton captures the request (headers/input become wrong):
// ❌ Captures request #1 forever; request #2..N read request #1's data.
$this->app->singleton(Service::class, function (Application $app) {
return new Service($app['request']);
});
Fix - pass a request resolver, or just hand the value in at call time:
// ✅ Option A: resolver closure
$this->app->singleton(Service::class, function (Application $app) {
return new Service(fn () => $app['request']);
});
// ✅ Option B (most robust): pass what you need at runtime
$service->method($request->input('name'));
The global request() helper always returns the request currently being handled, so it's safe. Type-hinting Illuminate\Http\Request on controller methods and route closures is also fine. [public-fact: docs]
Problem 3 - the classic memory leak: appending to a static:
public function index(Request $request): array
{
// ❌ This array grows on every request, forever, until the worker recycles.
Service::$data[] = Str::random(10);
return [/* ... */];
}
There's no clever fix here - don't accumulate into static state. Monitor worker memory in development, and remember --max-requests is your backstop, not your strategy. [public-fact: docs]
Our war story (the original substance). The nastiest version of this isn't the container - it's tenancy. In a multi-tenant app we run on Octane, a service was bound as a singleton and, on boot, resolved and cached the current tenant. Under PHP-FPM, that's harmless because the process dies after each request. Under Octane, the worker lives on, so the second tenant to hit that worker got the first tenant's connection and data - a silent, dangerous cross-tenant leak that never appears in a normal test run because tests don't reuse a warm worker. We reproduced it in isolation and pin the reproduction against a custom Octane branch while the upstream operation-service-providers work matures.
The durable lesson we've codified into our own engineering skill: treat every singleton and static as "does this hold something that changes per request?" - if yes, inject a resolver closure or pass it at call time, and never store per-request state where a worker can keep it.
Skip Octane when your app doesn't have the traffic or latency pressure to justify the extra operational discipline, or when it leans on packages/patterns that assume shared-nothing (fresh boot per request) and you can't audit them for state leaks.
Concretely, hold off if:
For everything else, high-throughput APIs, payment platforms, tenant SaaS, latency-sensitive dashboards, Octane is, in our experience, a large and reliable win once the state hygiene is in place.
Yes- laravel/octane is an official Laravel package installed via Composer.
In production, you typically still front Octane with Nginx (or use FrankenPHP's bundled Caddy) to serve static assets and terminate TLS, proxying dynamic requests to the Octane port.
--max-requests? 500 - the worker gracefully restarts after 500 requests to bound memory growth.
Only Swoole and Open Swoole. FrankenPHP and RoadRunner do not.
Run php artisan octane:reload on deploy so workers pick up new code.
Giorgi is a Software Architect and Laravel engineer at Redberry who has delivered production platforms across EV mobility, banking, car rental, and professional-services SaaS. He built E-space, an EV-charger marketplace, the ProCredit Bank website, and Skippit, a multi-tenant ERP for professional-service firms. He also co-founded Redberry's Laravel bootcamp and authors the company's PHP and Laravel developer tutorials.
Last updated on by

Redberry tops Clutch's Top 15 Laravel Developers list in the 2026 Clutch Global Awards - several years running.
Redberry is a sponsor of Laravel Live Denmark 2026, held August 20-21 at Werkstatt on Reffen in Copenhagen - the first year we have sponsored this event. Two of us will be there for it: Gaga Darsalia, our CEO, and Dati Chkhikvishvili, our Chief of Business.
At Laracon US 2026 our CEO Gaga Darsalia sat down with Eric Barnes of Laravel News to talk regulated fintech, AI-led legacy modernization, and why Redberry keeps coming back to Laracon.

We are a 200+ people agency and provide product design, software development, and creative growth marketing services to companies ranging from fresh startups to established enterprises. Our work has earned us 100+ international awards, partnerships with Laravel, Vue, Meta, and Google, and the title of Georgia’s agency of the year in 2019 and 2021.

