12 min read

Laravel Octane in 2026: Which Server to Pick and How to Avoid State Leaks

Giorgi Giunashvili

Delivery Manager & Software Architect

Laravel octane

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:

  • What Laravel Octane is: it boots your Laravel app once, holds it in memory, and feeds requests to persistent workers, instead of PHP-FPM's boot-handle-teardown on every request. Result: dramatically lower per-request overhead and higher throughput.
  • Which server to pick (short answer): FrankenPHP for most new apps and containerised/cloud deploys, RoadRunner for a mature, process-isolated monolith, Swoole when you specifically need coroutines, concurrent tasks, ticks, or the in-memory Octane cache/tables.
  • The trap to plan for from day one: because the app stays in memory, anything you store in a static property or a singleton persists between requests. That's the source of Octane's most surprising bugs. We cover the exact fix below.

What is Laravel Octane, and how is it different from PHP-FPM?

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. 

Which Octane server should I choose - FrankenPHP, Swoole, or RoadRunner?

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:

  1. FrankenPHP removes a moving part. Because it bundles the web server (Caddy) and TLS, a containerized deploy collapses to one process; that's why our default config/octane.php ships with 'server' => env('OCTANE_SERVER', 'frankenphp') Fewer moving parts in the request path are worth a lot operationally.
  2. Don't reach for Swoole unless you'll actually use its features. Swoole's headline abilities- 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

How does Octane work?

How does octane work

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.

Caveats to Consider

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.

Benchmarks

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:

  • Laravel with Octane
  • Laravel with Apache WebServer(traditional approach)
  • Laravel with its Built-In Server

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.

Benefits of Swoole

Benefits of Swoole

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.

What is the Octane state-leak / memory-leak gotcha - and how do I fix it?

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.

When should I NOT use Laravel Octane?

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:

  • Traffic is low and latency is fine. Octane's win is amplified under load; a brochure site or a low-volume internal tool won't feel it, and you've added a long-running process to babysit. [inference]
  • You depend on packages that hoard state (static caches, request captured in constructors) and can't easily audit or replace them — you'd be shipping their leaks into a persistent worker. [public-fact: docs caveats + inference]
  • Your team isn't ready for the mental model shift. Octane rewards developers who think about what lives between requests. If that discipline isn't in place, the bugs are subtle and the debugging is unpleasant. [internal-context]

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.

 

Frequently Asked Questions

Is Octane free / first-party?

Yes- laravel/octane is an official Laravel package installed via Composer.

Does Octane replace Nginx?

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.

What's the default?

 --max-requests? 500 - the worker gracefully restarts after 500 requests to bound memory growth.

Which servers support concurrent tasks and the Octane cache?

Only Swoole and Open Swoole. FrankenPHP and RoadRunner do not.

Do I need to restart after deploying?

Run php artisan octane:reload on deploy so workers pick up new code.

 

Updated on by

Giorgi Giunashvili

First published on

Giorgi Giunashvili

About the author

Giorgi Giunashvili

Delivery Manager at Redberry

Co-founded Redberry's Laravel Bootcamp

Built E-space, EV Marketplace

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.

Latest News

Aug
13

Number One Again: Redberry Tops Clutch's Spring 2026 Top Laravel Developers

Redberry tops Clutch's Top 15 Laravel Developers list in the 2026 Clutch Global Awards - several years running.

Aug
11

Sponsoring Laravel Live Denmark 2026 in Copenhagen

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.

Aug
5

Redberry on Laravel News at Laracon US 2026

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.

img

Meet the authors

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.

CONTACT US
img

Get in touch

Dati Chkhikvishvili

Chief Business Officer