Laravel Octane: the performance that comes with a persistent process
How Laravel Octane eliminates per-request bootstrap overhead — and the trade-offs that come with it.
Laravel Octane reached a stable release earlier this year. I had been following the project for a long time, and last month I finally had the chance to try it on a real application. It met my expectations — and then some. But it also raised the question: what’s the cost? Before answering that, it’s worth understanding why Octane makes such a difference in the first place.
PHP’s default model: starting from scratch on every request
PHP’s traditional execution model works like this: an HTTP request arrives, a PHP process starts, the entire framework bootstraps, the request is handled, and the process terminates. The next request repeats the same cycle.
This model is clean in many ways. State doesn’t bleed from one request to the next, memory issues resolve themselves, and a bug that crashes one request doesn’t affect the next.
But it has a cost: framework bootstrapping runs on every single request. Registering service providers, reading configuration files, defining bindings — all of this happens repeatedly. In a mid-sized Laravel application, from what I’ve measured myself, that’s roughly 20–50 ms of overhead per request — not a published benchmark, just a rough band from my own applications.
Octane’s model: a warmed-up process
As the Laravel 8.x Octane documentation defines it, Octane serves your application through “high-powered application servers” — at the time of writing, Open Swoole, Swoole and RoadRunner. (FrankenPHP, itself written in Go, was added to that list later; the trade-offs in this post largely apply to it too.) These tools run PHP as a persistent, long-running process: the application boots once, is kept in memory, and requests are fed to it.
The result: the first request is no longer more expensive than subsequent ones. The application is already warm and ready.
The setup is relatively straightforward:
composer require laravel/octane
php artisan octane:install
php artisan octane:start --server=swoole --port=8000
The response times I measured in my own test look roughly like this — again, not a published benchmark but my observation on a single application:
| Scenario | PHP-FPM | Octane (Swoole) |
|---|---|---|
| Simple route | ~15 ms | ~2 ms |
| Route with a database query | ~35 ms | ~18 ms |
| Complex route (many services) | ~80 ms | ~25 ms |
The exact numbers vary by application, but the ratio is consistent.
Trade-off 1: State leakage
This is the most critical issue. In PHP’s traditional model, state problems tend to resolve themselves — the next request starts in a fresh process. With Octane, the same process is reused across many requests, so static properties, singletons, and global variables can bleed between requests. The documentation warns about this under its own heading: because Octane keeps your application in memory between requests, adding data to a statically maintained array is a memory leak outright.
<?php
// THIS IS DANGEROUS — static property persists between requests
class RequestLogger
{
private static array $logs = [];
public static function add(string $message): void
{
self::$logs[] = $message; // Keeps accumulating on every request!
}
}
Octane provides listeners alongside the octane:start command — hooks that run before and after each request. But you do need to audit your existing code for this model.
Laravel’s own components are Octane-compatible — the docs say Octane “will automatically handle resetting any first-party framework state between requests”, but it does not always know how to reset the global state your own application creates. Problems typically surface in singletons you’ve written yourself or in third-party packages that weren’t built with persistent processes in mind: injecting the service container or the request into an object’s constructor leaves that object holding a stale copy on subsequent requests.
Trade-off 2: Swoole requirements
Swoole is installed as a C extension to PHP. It doesn’t ship with a standard PHP installation; the docs describe installing it through PECL (pecl install swoole). This narrows your hosting options.
RoadRunner is the alternative: as the docs put it, it is powered by a binary built using Go, and it runs standard PHP as a long-running process. It’s not as fast as Swoole, but the setup involves considerably less friction.
Trade-off 3: Development environment
One thing not to get wrong here: changes are not picked up on their own. The documentation is explicit — since the application is loaded into memory once when the server starts, changes to your files will not be reflected when you refresh the browser; even a route added to routes/web.php is ignored until the server restarts. The --watch flag performs that restart for you. It provides an environment close to production, but it doesn’t behave exactly the same as traditional PHP-FPM.
You’ll also want to verify that your debug tools and profilers work properly with Octane.
When it’s worth it
Octane isn’t the right choice for every project. I wouldn’t recommend migrating a low-traffic content site or an admin panel — the gains there are invisible, and the risk is unnecessary.
For high-traffic APIs, real-time features, or applications where response time is actively measured and monitored, it’s a meaningful improvement. In particular, where you need WebSocket support or concurrent task execution, the additional capabilities Swoole brings are valuable in their own right.
If you’re migrating an existing project, start by auditing your state management. Review static property usage, global state, and singletons. That cleanup often improves the project independently of Octane.
Sources
Questions on this topic
Should I put my API server behind a Cloudflare Tunnel and close port 443 to the internet?
The tunnel is sound but makes Cloudflare your only way in; stay on the current Caddy setup, lock the origin to Cloudflare's IP ranges and add origin pulls.
How do I stop OPcache from causing 500s during a zero-downtime DeployerPHP deploy?
After flipping the symlink, gracefully reload PHP-FPM rather than restarting it; on Octane, octane:reload is mandatory and opcache_reset is optional.
How do I set up a Circuit Breaker for a flaky third-party API dependency?
A breaker alone won't hold: pull `connect_timeout` and `timeout` down to 800ms-2s, isolate the dependency behind a bulkhead, and define a cached fallback.
How do I do graceful shutdown on SIGTERM during autoscaling scale-in?
On SIGTERM drain the load balancer first, finish in-flight work inside the server.Shutdown(ctx) deadline, then align the grace period with that drain.
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.
Comments
Sign in with your GitHub account to join the discussion. Comments are stored in GitHub Discussions.