Laravel Octane and FrankenPHP: Gains and Traps
Octane removes the bootstrap from every request. It also removes the guarantee that every request starts clean.
The traditional PHP model boots the framework, handles one request, and throws everything away. That is slow, and it is also why PHP is so forgiving: no request can contaminate the next one. Octane keeps the application in memory across requests. The speed comes from precisely the same property that introduces the bugs.
Where the gain actually is
Bootstrap is a fixed cost per request — service providers, config, routes, container bindings. On a typical application that is a few tens of milliseconds. Removing it is a large relative win on a fast endpoint and close to noise on one that waits 400ms for a database.
- API endpoints returning small payloads — the clearest wins
- high-concurrency workloads made of short requests
- applications with heavy service provider registration
- not: report generation, exports, or anything dominated by query time
If your 95th percentile is dominated by a slow query, Octane will not fix it. Fix the query. The framework overhead you are removing is not where the time is going.
FrankenPHP, Swoole, RoadRunner
- FrankenPHP — PHP embedded in a Go web server; a single binary, HTTP/2 and HTTP/3, worker mode. Backed by the PHP Foundation and the natural fit for containers
- Swoole — the most features, including coroutines and tasks, and the largest behavioural surface to learn
- RoadRunner — a Go supervisor over PHP workers; conservative and predictable
For a new deployment FrankenPHP is the reasonable default, largely because it collapses the web server and the runtime into one process you can ship as an image.
The traps are all state
Anything that survives between requests is now a potential bug. These are the ones that actually show up in production.
- singletons holding request data — a service resolved once, caching the first user's context
- static properties quietly accumulating across requests
- closures in service providers capturing the request or the container at boot
- global config mutated at runtime, so one request's change now affects everyone
- memory growth from anything appending to a static array
// leaks: $this->user is set once, on the first request, forever
class Reporter {
protected $user;
public function for($user) { $this->user ??= $user; return $this; }
}
// safe: no request state survives the response
class Reporter {
public function __construct(private User $user) {}
public function for(User $user): self { return new self($user); }
}
The symptom is the worst kind available: occasional, user-visible data leaking between sessions, never reproducible in testing, because testing does not reuse a warm worker.
Operational differences
- deploys must restart workers, or code changes are simply not picked up
- memory is a hard per-worker constraint, and a leak compounds instead of resetting
- long-running workers hold database connections; connection limits need revisiting
- worker count is now your concurrency limit, replacing FPM max_children arithmetic
Watch worker memory over hours rather than minutes. A leak adding 2MB per request is invisible in a smoke test and fatal overnight.
Laravel 13 and PHP 8.4
If you are planning this work, do it against Laravel 13 on PHP 8.4 rather than upgrading twice. Property hooks remove a category of accessor boilerplate, and asymmetric visibility makes it considerably easier to build the immutable, request-scoped objects that Octane rewards.
The honest comparison
A correctly sized FPM pool on tuned hardware is not slow. Octane's advantage is real, but it is a multiplier on framework overhead rather than on your application logic, and it buys that with a class of bug PHP developers have never previously had to think about. Measure your bootstrap cost first. If it is 8% of your response time, you now know your ceiling.
Run Octane in staging under real traffic patterns for a week before production. State bugs are a function of worker reuse, and a worker that has served ten requests has not been tested.