Most Laravel apps serve requests through PHP-FPM, which wipes the process clean between requests. Laravel Octane keeps the application in memory across requests, and that single difference is where both its speed and its worst bugs live. With PHP 8.5 now shipping, this guide covers what Octane actually saves, which PHP 8.5 features matter for a long-lived worker, and the octane state management failure modes that turn a fast benchmark into a production incident.
Octane does not make your code faster. It removes per-request bootstrap cost, typically the framework boot plus container rebuild. Everything that depends on a fresh request lifecycle, including static properties, singletons holding request data, and unflushed state, becomes a cross-request leak you must handle yourself.
What Octane actually saves per request
Under FPM, every request pays for: composer autoload resolution, framework boot, service provider registration and booting, and middleware pipeline construction. On a mid-sized Laravel app that bootstrap is usually tens of milliseconds before your first line of controller code runs. Octane boots the application once, then replays only the request-specific parts.
The honest way to verify this on your own app: run php artisan octane:start locally, hit an endpoint with and without Octane, and compare with curl -w "%{time_total}\n" over a few hundred sequential requests. The gap you see is bootstrap cost, not your logic getting faster. If the gap is small, your app was not spending much time booting, and Octane buys you little. Apps with heavy service providers, many package bootstrappers, or large route maps see the biggest laravel octane performance wins.
Which PHP 8.5 features matter for Octane
PHP 8.5 lands several changes that interact differently with a persistent worker than with FPM. The full list is in the official PHP 8.5 release notes, but three are worth attention for Octane specifically.
The php 8.5 pipe operator is worker-safe, with a caveat
The new pipe operator lets you pass a value through a chain of callables left to right:
$result = $payload
|> normalize(...)
|> fn ($data) => Validator::make($data, $this->rules())
|> fn ($v) => $v->validated()
|> fn ($data) => Model::create($data);This is a readability upgrade, not a performance one. The caveat for Octane: pipes execute eagerly and inline, so anything you pipe through runs in the worker process. Keep CPU-bound transforms in the pipe; push anything slow or flaky to a queued job instead. If you are deciding between sync work and queued work inside a request, the trade-offs are the same as for sync versus queued listeners.
Fatal error backtraces help Octane debugging
Octane workers that hit a fatal error previously died with minimal context, and repeated fatals could drain your worker pool. PHP 8.5 includes backtraces for fatal errors, so a worker crash log now shows the call chain that killed it. This makes octane.log and your container logs materially more useful during a memory or type failure. Verify by deliberately triggering a fatal in a worker and reading the trace in your log output.
clone with reduces defensive copies
The new clone with syntax lets you clone an object while overriding properties in one expression. In Octane apps where DTOs and value objects get reused across requests, this keeps copy-and-modify patterns concise:
$retryContext = clone $context with (attempt: $context->attempt + 1);Choosing a driver: Swoole vs octane FrankenPHP
Octane supports several application servers. The choice affects memory model and deployment story more than raw speed.
| Concern | Swoole / Open Swoole | FrankenPHP |
|---|---|---|
| Deployment | PHP extension, needs matching build | Static binary, ships as a Docker image |
| Coroutines | Yes, enables concurrent task support | No coroutine API in Octane context |
| Ops familiarity | More moving parts to pin versions on | Closer to standard PHP, simpler CI |
Recommendation: if you already run containers and want the least operational surface, start with FrankenPHP. Reach for Swoole only when you need Octane's concurrent task features, and read the Octane docs on the Laravel Octane page before committing, because coroutine-safe code is a real constraint, not a checkbox.
The state leaks that actually bite
In systems I've tuned, the failures cluster into four patterns. Each one works fine under FPM and only breaks under Octane.
1. Static properties holding request data
A helper class with static::$currentUser set in middleware becomes a security bug: request two reads the user from request one. Under FPM the static dies with the request; under Octane it survives. The fix is to never cache request-scoped data in statics. Resolve it from the container per request.
2. Singletons that capture the request
A singleton bound at boot that type-hints Request in a method captures whatever request was active when it was first resolved. Use scoped bindings or resolve the request inside the method call.
3. Unflushed framework state
Queued listener callbacks, pending mail, and validation error bags can carry over if a request throws mid-lifecycle. Octane fires events like RequestReceived and RequestTerminated where the framework resets most of this, but third-party packages that stash state on the app container are common leak sources. When a package misbehaves under Octane, look for container singletons or static caches in its source before filing a bug.
4. Memory growth from accumulated references
Long-lived workers amplify anything that keeps references alive: collections stored on properties, event listeners appended per request, or large query logs. Watch worker memory limits, or restart workers after N requests as a mitigation, and confirm the leak by charting worker RSS in your container metrics over an hour of traffic.
Run your full test suite with octane state management semantics in mind: tests that pass under FPM can hide cross-request leakage. A cheap smoke test is hammering the same endpoint twice with different auth headers and asserting the second response never contains the first user's data.
Does Octane still make sense in 2026?
Octane is a trade: you buy lower latency per request and pay with a stricter discipline around state. For high-traffic API endpoints, websocket-adjacent workloads, and apps where you control the packages, it is worth it. For a CRUD app on a single server where FPM latency is already acceptable, the operational risk outweighs the milliseconds. Measure your own bootstrap cost first, audit your dependencies for static state second, and only then decide. If your bottleneck turns out to be database load rather than PHP, fix that side first; the eager-loading patterns in fixing Eloquent N+1 at scale usually pay back more than an application server swap.
