PHP Runtimes Explained: FPM vs Octane vs FrankenPHP vs Swoole vs RoadRunner
Direct answer: PHP-FPM boots your app fresh on every request: simple, isolated and still the default. Swoole, RoadRunner and FrankenPHP keep the app in memory between requests, which removes bootstrap cost and typically gives several times more throughput on framework-heavy endpoints. Laravel Octane isn't a runtime; it's Laravel's adapter for those three. Choose FPM for simplicity, FrankenPHP for the easiest modern setup, Swoole when you need coroutines and in-process concurrency, and RoadRunner if your team lives in Go tooling.
We run Laravel Octane on Swoole in production at MarginTop and publish our own Swoole + io_uring runtime image. This comparison is part of my PHP in 2026 series.
The two execution models#
Shared-nothing: boot, handle, forget#
Classic PHP (FPM, mod_php, CGI) starts every request with a clean slate: load the framework, register services, handle the request, throw everything away. Opcache keeps compiled code in memory, but your application's objects, container and config are rebuilt every time.
That's PHP's superpower and its cost. A memory leak or a global variable can't outlive one request, but a large framework spends milliseconds booting before it does any real work.
Long-running workers: boot once, serve many#
Swoole, RoadRunner and FrankenPHP's worker mode boot your application once per worker and then feed it request after request. Bootstrap disappears from the hot path. The price is that state now survives between requests: static properties, singletons and anything you cache on an object. That's the source of almost every Octane bug.
Side-by-side comparison#
| Runtime | What it is | Model | Best for | Watch out for |
|---|---|---|---|---|
| PHP-FPM | PHP's FastCGI process manager, behind nginx or Caddy | Shared-nothing | Most apps, simple ops, shared hosting, maximum compatibility | Bootstrap cost per request, and worker count limits concurrency |
Apache mod_php | PHP embedded in Apache | Shared-nothing | Legacy setups | Ties PHP to Apache's process model. It was the highest-risk setup in PHP2Shell. |
| FrankenPHP | A PHP app server written in Go on top of Caddy | Classic or worker mode | New projects, single-binary deploys, automatic HTTPS, HTTP/2 and HTTP/3 | Worker mode has the same state-leak rules as any long-running runtime |
| Swoole | A C/C++ PHP extension with its own HTTP server and coroutine scheduler | Long-running plus coroutines | High concurrency, WebSockets, parallel I/O inside a request | Steeper learning curve. Some extensions and libraries aren't coroutine-safe. |
| RoadRunner | A Go application server that manages PHP worker processes | Long-running | Teams wanting queues, gRPC and plugins in one Go binary | Another moving part outside PHP to configure and monitor |
| Laravel Octane | Laravel's adapter that runs your app on Swoole, RoadRunner or FrankenPHP | Uses the driver's model | Laravel apps wanting long-running performance with framework support | Your code must be safe to reuse across requests |
FrankenPHP: the newest option, now officially backed#
FrankenPHP, created by Kévin Dunglas, embeds PHP inside the Caddy web server. One binary gives you a web server with automatic HTTPS, HTTP/3, early hints and an optional worker mode. In May 2025 the PHP Foundation announced official support for FrankenPHP, and the project moved under the PHP GitHub organisation. That's a strong signal it's here to stay.
Its biggest advantage is the gentle on-ramp. You can run it in classic mode as a drop-in replacement for nginx + FPM, then switch to worker mode when you're ready.
Swoole: when you need concurrency, not just speed#
Swoole is the only option here that gives PHP coroutines: lightweight tasks that yield while waiting on I/O. That enables parallel database and HTTP calls inside one request (Octane's Octane::concurrently()), WebSocket servers and long-lived connections in the same process. That is why it fits device-connected and real-time workloads like many of the systems we build. The details are in how we use Octane + Swoole, and the async picture is in PHP async in 2026.
RoadRunner: a Go supervisor for PHP workers#
RoadRunner, from Spiral Scout, is a Go server that keeps a pool of PHP worker processes and talks to them over a fast binary protocol. It bundles plugins for HTTP, queues, gRPC, key-value storage and more. It's a good fit if you like configuring infrastructure in one YAML file and want PHP to stay plain PHP, with no extension to compile.
The rule every long-running runtime shares#
Whatever you pick, the same discipline applies once workers live longer than a request:
- No request data in static properties or singletons. The current user, tenant or locale must be resolved per request.
- Reset what you cache. Octane and frameworks provide hooks for this. Use them.
- Recycle workers. Set a max-requests limit so slow memory leaks can't accumulate.
- Reload on deploy. Workers keep old code in memory until they restart. That also applies to security patches: after PHP2Shell, upgrading the package did nothing until workers restarted.
How to choose#
| If you… | Choose |
|---|---|
| Want the simplest, most compatible setup, or performance is already fine | PHP-FPM behind nginx or Caddy |
| Are starting fresh and want one binary with HTTPS and HTTP/3 built in | FrankenPHP (classic mode first, worker mode later) |
| Need WebSockets, coroutines or parallel I/O inside a request | Swoole (via Octane for Laravel) |
| Want queues, gRPC and HTTP in one Go-managed server | RoadRunner |
| Run Laravel and bootstrap time is a measured bottleneck | Octane on whichever driver above fits |
One warning from experience: profile first. If your slow endpoint spends 300 ms in the database, switching runtimes saves you the 10–20 ms of bootstrap and nothing else. Fix the queries, then change runtimes.
Key takeaways#
- FPM boots per request (simple and isolated). Swoole, RoadRunner and FrankenPHP boot once and reuse workers (much faster, but state leaks are possible).
- Laravel Octane is an adapter, not a runtime. It runs on Swoole, RoadRunner or FrankenPHP.
- FrankenPHP has been officially supported by the PHP Foundation since May 2025 and is the easiest modern option.
- Swoole is the choice when you need coroutines and in-process concurrency.
- Long-running workers must be reloaded to pick up code changes and security patches.