Originally published at hafiz.dev
In April 2026, a carefully controlled benchmark put all three Laravel Octane servers through a real Laravel workload. RoadRunner more than doubled PHP-FPM's throughput. Swoole, the server with a decade-long reputation as PHP's performance monster, finished dead last. Below plain FPM.
A month later, a hosting company that has migrated dozens of Laravel apps to Octane published their production numbers. Their verdict on Swoole: it's their fastest option for API-heavy workloads, and they deploy it deliberately for exactly those customers.
Both sources are competent. Both published their methodology. Both are right.
That contradiction is the most useful thing you can learn about choosing an Octane server, because it tells you the question "which server is fastest?" has no universal answer. The useful question is which server matches your workload, your deployment model, and your team's appetite for operational complexity. This post covers what Octane actually changes, how the three servers differ architecturally, why the benchmarks disagree, and a decision framework you can apply to your own app. I'll also tell you when the honest answer is to not use Octane at all.
What Octane Actually Changes
A standard Laravel request on PHP-FPM boots the entire framework from zero. Service providers register, config loads, the container wires up, and then your code finally runs. For a typical app that boot costs 10 to 30ms per request, more if you're carrying Filament, Nova, or a heavy stack of packages. Then the request ends and everything is thrown away.
Octane inverts that. The application boots once per worker, stays resident in memory, and each incoming request reuses the already-built framework. The boot tax disappears from every request except the first.
That's the whole trick. Octane doesn't make your database queries faster, doesn't cache your responses, and doesn't parallelize your code (with one Swoole-shaped exception we'll get to). If your endpoint spends 200ms in MySQL, Octane saves you the 20ms boot and leaves the 200ms untouched. This is why I tell people to read their profiler before reading Octane benchmarks: if framework bootstrap isn't a meaningful slice of your response time, start with query optimization instead. It's usually the bigger win and it carries zero migration risk.
Installation is the same regardless of server:
composer require laravel/octane
php artisan octane:install --server=frankenphp
# or --server=swoole, --server=roadrunner
Octane supports four servers: FrankenPHP, Swoole, Open Swoole, and RoadRunner. Open Swoole is a community fork with the same Octane feature set as Swoole, so practically the choice is between three architectures.
Three Servers, Three Architectures
The performance and failure characteristics of each server come directly from how a request physically reaches your PHP code.
FrankenPHP is a Go binary built on the Caddy web server, with the official PHP interpreter embedded directly into the process via cgo. There's no proxy hop and no inter-process communication: Caddy accepts the request and hands it to a PHP thread inside the same process, where your booted Laravel app is waiting. Because Caddy is the web server, you get HTTP/2, HTTP/3, and automatic HTTPS without putting Nginx in front. Octane downloads the FrankenPHP binary for you when you pick it during install.
RoadRunner is also written in Go, but it takes the opposite approach to integration. It runs as a standalone server binary that manages a pool of ordinary PHP processes, forwarding requests to them over a fast RPC protocol. Your PHP workers are regular processes with real isolation: if one worker corrupts its state or dies, RoadRunner respawns it and the others never notice. It's been doing this job since 2018 and has the most mature plugin ecosystem of the three (queues, gRPC, key-value storage).
Swoole is different in kind, and the difference goes deeper than plumbing. It's a C extension installed via PECL that replaces PHP's execution model with an event loop and coroutines, closer to Node.js than to classic PHP. When your code hits blocking I/O, Swoole can suspend that coroutine and run another request on the same worker. That's a capability the other two simply don't have, and it's also why Swoole demands the most from your code.
The architectural differences cascade into everything else, so let's take each server on its own terms.
FrankenPHP: The Deployment Story
FrankenPHP is the youngest of the three, released in late 2023 and hardened through 2024 and 2025. What it changed isn't raw speed. It's how little infrastructure you need.
One binary is your web server, TLS manager, static file server, and PHP runtime. A production Dockerfile is this small:
FROM dunglas/frankenphp
COPY . /app
WORKDIR /app
RUN composer install --no-dev --optimize-autoloader
ENTRYPOINT ["php", "artisan", "octane:frankenphp"]
One container, one port, no Nginx, no supervisor config. If you deploy to Cloud Run, Kubernetes, or any autoscaling container platform, that shape maps perfectly. And on classic VPS setups, HTTPS with automatic certificate renewal comes from Caddy for free. When you need to go beyond the defaults, php artisan octane:start --server=frankenphp --caddyfile=/path/to/Caddyfile gives you full Caddy configuration.
The honest trade-offs: it's the youngest option, and youth shows. One hosting provider that runs it in production reports hitting two memory-leak regressions over 18 months, both fixed promptly upstream, but that's a different stability track record than RoadRunner's. Configuration is Caddyfile syntax rather than the Nginx configs most Laravel developers have committed to muscle memory. And in benchmarks driven by HTTP/1.0 tools, FrankenPHP looks unremarkable, for reasons we'll get to.
Swoole: The Ceiling and the Cliff
Swoole has the highest performance ceiling of the three, and the steepest cliff next to it.
The ceiling comes from coroutines. While FrankenPHP and RoadRunner workers handle one request at a time, Swoole's runtime can juggle many, suspending whichever one is waiting on the database. For endpoints that fan out to multiple upstream services, Octane exposes this directly:
use Laravel\Octane\Facades\Octane;
[$orders, $stats, $flags] = Octane::concurrently([
fn () => Order::recent()->get(),
fn () => Cache::get('dashboard:stats'),
fn () => Feature::all(),
]);
Three operations, dispatched to Swoole's task workers and executed concurrently while your request waits for the results. You size that pool with --task-workers on octane:start. Octane::concurrently(), ticks, intervals, and the Swoole-backed Octane cache and tables are exclusive to Swoole and Open Swoole. Neither FrankenPHP nor RoadRunner can offer them because their workers don't have a coroutine scheduler.
The cliff: Swoole changes PHP's execution semantics, and code written for classic synchronous PHP doesn't always survive the change. Extensions can conflict, some packages misbehave inside coroutines, and Xdebug famously doesn't cooperate, so production profiling means Blackfire or Tideways. It's also an extension you compile via PECL rather than a binary Octane downloads for you, which makes your Docker images and CI more bespoke. The production shops that get the most from Swoole recycle workers aggressively (max_requests around 250 versus 500 on RoadRunner) because memory grows faster.
My blunt read: Swoole is a specialist's tool. If you're not going to use concurrently() or your workloads don't have real I/O concurrency, you're paying Swoole's complexity tax for a capability you never invoke.
RoadRunner: The Boring Choice, Compliment Intended
RoadRunner wins on exactly one axis, and it happens to be the axis that matters most for a lot of teams: predictability.
Its worker isolation model means a leak or crash in one PHP process can't poison its siblings. The Go server respawns dead workers and traffic continues. It has seven-plus years of production history, and its configuration lives in a plain YAML file that does what it says. In the April 2026 benchmark that embarrassed Swoole, RoadRunner delivered 111.6% more throughput than PHP-FPM on the same hardware with a 41% lower p99. Not because it's exotic, but because boot-once plus process isolation is a simple model that performs consistently across workload shapes.
Setup needs two extra packages alongside Octane, and the rr binary is offered for download on first start:
composer require laravel/octane spiral/roadrunner-cli spiral/roadrunner-http
php artisan octane:start --server=roadrunner
What you give up: no HTTP/3 or automatic HTTPS (you'll keep Nginx or Caddy in front), no coroutines, and a plugin ecosystem (gRPC, queues) that most Laravel apps never touch because Laravel's own queue system already covers that ground.
Why the Benchmarks Disagree
Back to the contradiction from the intro, because resolving it is what makes you dangerous in this decision.
The April 2026 benchmark that placed Swoole below PHP-FPM used ab, a load tool that speaks HTTP/1.0 with short-lived connections, against a light-I/O Laravel route. Two things follow. First, Swoole's entire advantage is concurrent I/O scheduling; on a workload with barely any I/O wait, its coroutine scheduler is pure overhead, so it loses. Second, FrankenPHP's HTTP/2 multiplexing never gets exercised over HTTP/1.0, so it benchmarks like plain FPM while its real-world strength stays invisible.
Meanwhile, the production shop reporting Swoole as their fastest option runs it on API workloads full of upstream calls and database fan-out, with concurrently() in active use. That's precisely the workload where coroutines pay. Same server, opposite conclusion, and both measurements are honest.
The lesson generalizes: every Octane benchmark measures its own workload and tooling as much as it measures the server. A hosting provider's before-and-after on a real authenticated endpoint (4 to 5x throughput moving FPM to FrankenPHP with Octane) is more predictive for a typical app than any synthetic table. And your own app benchmarked for an afternoon beats both. So treat published numbers as bounds, not rankings.
The Decision Framework
Here's how I'd actually choose in 2026.
Pick FrankenPHP if you deploy in containers or want the simplest possible stack. Cloud Run, Kubernetes, Docker-based platforms, or a VPS where you'd rather not maintain Nginx. It's also the right default if you're new to Octane: the binary auto-installs, and the single-process model has the fewest moving parts to reason about.
Pick RoadRunner if you're migrating a large existing app and stability is the priority. The process isolation gives you the softest failure modes while you shake out the state bugs every migration surfaces. It's the conservative choice, and for a revenue-carrying monolith, conservative is correct.
Pick Swoole only if you'll use what makes it Swoole. Many concurrent upstream calls per request, real fan-out, a team comfortable auditing packages for coroutine safety. Then the ceiling is real and nothing else reaches it.
Pick none of them if your profiler says boot time isn't your problem. A 300ms endpoint that spends 260ms in queries gets nothing meaningful from Octane. The cheap performance wins come first; Octane is what you reach for after them, when traffic and infrastructure cost make the boot tax worth eliminating. My own apps still run plain FPM on small droplets, and that's deliberate: at their traffic, Octane would add operational surface without moving anything a user can feel. The infrastructure cost math only tips toward Octane when you're scaling servers to handle load that's mostly boot overhead.
The Gotchas That Apply to All Three
Whichever server you pick, the migration risk is identical, because it comes from Octane's model rather than the server underneath.
Your app now lives in memory across requests. Anything static, any singleton, any memoized value persists into the next request. The classic bug is a package caching the authenticated user in a singleton, so request N+1 sees request N's user. Audit your singletons, use Octane's flush hooks for anything request-scoped, and test with two different logged-in users hitting the same worker.
Memory grows. Every long-running PHP process leaks a little, so set --max-requests and let workers recycle themselves. Deploys change too: php artisan octane:reload swaps workers with zero downtime, and it belongs in your deploy script (the full command list is in the Laravel Artisan Commands reference). And remember each worker handles one request at a time (Swoole's coroutines aside), so a slow request blocks a worker slot. Anything slow belongs on a queue, same as always.
FAQ
Is Laravel Octane faster than PHP-FPM for every application?
No. Octane eliminates the framework boot cost, roughly 10 to 30ms per request on a typical app. If your response time is dominated by database queries or external APIs, Octane changes almost nothing. Profile first: it pays off when boot time is a meaningful fraction of your responses and traffic is high enough that the saved milliseconds compound into fewer servers.
Which Octane server is easiest to set up?
FrankenPHP. Octane downloads the binary for you during octane:install, and because it bundles Caddy you don't need a separate web server for HTTPS, HTTP/2, or HTTP/3. RoadRunner is close behind (two extra Composer packages, auto-downloaded binary). Swoole requires compiling a PECL extension, which makes it the most involved.
Do I need to rewrite my code for Octane?
Rewrite, no. Audit, yes. Static properties, singletons, and anything memoized will persist between requests, which is the source of nearly all Octane bugs. Most well-structured apps need a handful of fixes, not an architectural change. Third-party packages are the bigger unknown, so test the critical paths under a single worker with --max-requests=1 to surface state leaks quickly.
What about Open Swoole?
Fully supported by Octane and functionally equivalent for Octane's purposes: you get the same concurrent tasks, ticks, and intervals as Swoole. It's a community fork, so the choice between them usually comes down to which extension your platform and PHP version support more cleanly.
Does Octane work with Laravel 13?
Yes. Octane is a first-party package and the Laravel 13 documentation covers all four servers. Install with composer require laravel/octane and pick your server during octane:install.
Wrapping Up
The 2026 Octane question isn't "which server wins the benchmark". It's a matching problem. FrankenPHP matches container-native deployments and small teams that want one binary doing everything. RoadRunner matches big migrations where boring is a feature. Swoole matches I/O-heavy APIs run by teams who'll actually use its concurrency. And plain PHP-FPM still matches every app whose profiler says boot time isn't the bottleneck, which is more apps than the benchmark posts admit.
Pick by workload, verify with an afternoon of load testing on your own endpoints, and be suspicious of any comparison (including this one) that hands you a single winner.
Planning an Octane migration for a production Laravel app and want a second pair of eyes on the state-leak audit and server choice? Let's talk.