FrankenPHP is a PHP application server based on the Go web server Caddy, which unites PHP execution and web server in a single binary and eliminates the classic bootstrap overhead of PHP-FPM through its worker mode. For frameworks like Laravel, this means: The application boots once and then remains in memory, instead of restarting with every request. In combination with Laravel Octane, FrankenPHP is considered today as one of the more performant PHP server options.

As a Laravel agency focused on professional web development, we at mindtwo deal daily with performance optimization of PHP applications, from classic caching to modern server architectures. This article explains how FrankenPHP works technically, how it compares to alternatives like Swoole and RoadRunner, and when the migration is actually worthwhile.

Why FrankenPHP Challenges Classic PHP-FPM

Anyone who has been running a PHP application for years knows the pattern: Every single HTTP request starts from zero. In a classic PHP-FPM setup, each request goes through the complete application bootstrap anew, with autoloading, service container, configuration files, and routing tables. The framework "awakens" thousands of times a day, only to "die" again after a few milliseconds.

This cold-start cycle has grown over decades because it elegantly solves a crucial problem: isolation. Each request gets a completely fresh state, nothing "leaks" from one request to the next. The price for this is performance. Especially with complex, container-heavy frameworks like Laravel or Symfony, the repeated bootstrap is noticeably significant.

Anyone who has already dealt with classic PHP performance optimization knows the usual control levers from our article on fast PHP websites and applications: OPcache, Redis caching, optimized database queries, and framework-level caching. These are important and effective measures, but they optimize within an existing server model. They don't solve the structural problem that the application completely restarts with every request.

Previous Solution Approaches and Their Limitations

Before we look at FrankenPHP in detail, it's worth taking a brief look at what the PHP world has already undertaken to reduce bootstrap overhead:

  • OPcache: Caches the compiled opcode of PHP files in shared memory, so that parsing and compilation don't have to be repeated with every request. A mandatory feature for any production environment, see also our lexicon entry on PHP OPcache.
  • Preloading (since PHP 7.4): Loads defined classes into memory at server startup, instead of resolving them per request.
  • FPM Pool Tuning: Adjustment of pm.max_children, pm.max_requests & co., to tune PHP-FPM's process management as efficiently as possible to the respective server hardware.

These measures are effective, but structurally limited. They reduce the overhead per request. However, they don't prevent the complete application state, i.e., service container, loaded objects, and established connections, from being discarded after every request and rebuilt with the next request. This is exactly where FrankenPHP's fundamentally different architectural approach comes in.

FrankenPHP as an Architectural Answer: From Caddy to Runtime

FrankenPHP was developed by Kévin Dunglas and is based on Caddy, a modern web server written in Go, known among other things for automatic HTTPS and native HTTP/2 and HTTP/3 support. FrankenPHP integrates the PHP engine directly as a module into this Go server. There is no longer a need for an additional reverse proxy in front of a separate PHP process, but rather a single binary that unites web server and PHP execution. Another feature often underestimated in practice: FrankenPHP supports HTTP-103 Early Hints natively as one of the few PHP SAPIs, allowing browsers to load critical resources like CSS or fonts while the actual response is still being generated.

The decisive difference from classic setups lies not only in that PHP and web server run in one process, but in how this process handles requests. And this leads us directly to the core of FrankenPHP: the worker mode.

The Anatomy of a Request in Worker Mode

Worker mode is the actual reason why FrankenPHP performs in a different league than classic PHP-FPM. Instead of starting a new PHP process or at least a complete bootstrap cycle for each request, the process works like this:

  1. One-Time Boot: When a worker starts, the application, such as a Laravel project, boots completely exactly once. Service container, routing, and configuration are built once and then remain in the worker process's memory.
  2. Request Loop: The worker runs in an endless loop and waits for incoming requests. When a request arrives, the already initialized application processes it directly, without a new bootstrap.
  3. State Cleanup Between Requests: After each request, FrankenPHP ensures that certain global states are reset so that no request state "seeps through" to the next. Superglobals like $_GET, $_POST, or $_SERVER are refilled per request.
  4. Reuse Instead of Restart: The worker process remains active over thousands of requests, until it restarts, for example during a deployment or for stability reasons after a configurable number of requests.

The performance gain results directly from step 1: The expensive bootstrap process is eliminated for all requests except the very first per worker. For frameworks with extensive service containers, like Laravel, this can save the majority of request time that was previously consumed for pure "startup".

However, this architecture also brings a new responsibility: Developers must actively ensure that no state accidentally persists between requests, for example through static properties, singleton instances with request-specific data, or unreset service container bindings. This class of bugs, broadly called "memory leaks", simply doesn't exist in classic PHP-FPM setups because each request gets a completely fresh process anyway.

Reverse Proxy & Process Model Comparison: FrankenPHP vs. PHP-FPM, Swoole, and RoadRunner

FrankenPHP is not the first attempt to free PHP from its classic request-response cycle. A comparison with established alternatives helps to better understand the positioning:

PHP-FPM (classic) Process model: Pool of PHP worker processes, each going through a complete bootstrap per request. Advantage: maximum isolation, extremely mature, works with practically any PHP code without modification. Disadvantage: bootstrap overhead with every request, no persistent application state.

Swoole A PHP extension that introduces an asynchronous, event-loop-based execution model directly into PHP, including coroutines. Swoole is very performant, but often requires deeper interventions in the application code and a rethinking towards asynchronous programming. The learning curve is correspondingly steeper.

RoadRunner An application server written in Go, conceptually not dissimilar to FrankenPHP: Here too, a persistent worker process runs, keeping PHP applications alive in the background. However, RoadRunner requires a separate reverse proxy in front of it, for example Nginx, while FrankenPHP unites web server and application server in one binary through Caddy integration.

FrankenPHP Combines RoadRunner's worker mode approach with native web server integration via Caddy, including automatic HTTPS and HTTP/3 support. Additionally, FrankenPHP also supports a classic, non-persistent mode, enabling a smooth migration without immediately depending on worker mode compatibility of the entire code.

The central difference from Swoole is that FrankenPHP stays closer to the classic PHP execution model and requires less profound code adjustments. RoadRunner is architecturally the closest relative, but without the integrated web server layer.

FrankenPHP and Laravel Octane: The Practical Combination

For Laravel projects, FrankenPHP is particularly relevant in combination with Laravel Octane. Octane provides a unified abstraction layer for application servers like Swoole, RoadRunner, and FrankenPHP and handles the critical tasks necessary for worker mode operation:

  • Resetting service container bindings between requests
  • Handling singleton instances that accidentally hold request state
  • Providing lifecycle hooks (e.g., Octane::tick()) for periodic tasks in the worker

The combination of Octane and FrankenPHP is currently considered one of the more attractive options in the Laravel ecosystem, as both components were specifically developed or extended for this interaction. Especially for teams already working in the Laravel environment, the migration to FrankenPHP is thus significantly lower risk than a switch to Swoole, as Octane already abstracts the biggest pitfalls.

Limitations and Risks: When FrankenPHP Is (Still) Not the Right Choice

Despite the convincing architecture, FrankenPHP is not a no-brainer for every project. Some points that should be checked before a migration:

  • Legacy Code with Global State: Older applications that rely heavily on global variables, static properties, or uncontrolled singletons can lead to subtle bugs in worker mode that would never have occurred in classic PHP-FPM setups.
  • Memory Leaks: Since worker processes run over many requests, small memory leaks that would be irrelevant in short-lived FPM processes accumulate into a real problem in worker mode.
  • Ecosystem Maturity: FrankenPHP is significantly younger than PHP-FPM. Not every PHP package is explicitly tested for worker mode compatibility, although the situation has been continuously improving since 2025. The revised worker API introduced with FrankenPHP 2.0 also shows that the API can still change fundamentally at this early stage. Existing worker setups, for example with Symfony, had to be partially adapted in the course of this update.
  • Debugging Complexity: Errors that only occur after hundreds of requests in the same worker are harder to reproduce than classic, isolated request bugs.

Decision Matrix: Is the Migration to FrankenPHP Worthwhile?

FrankenPHP is particularly well suited when:

  • the application suffers from noticeable bootstrap overhead (complex service containers, many routes, extensive configuration)
  • Laravel with Octane support is already in use or planned
  • native HTTP/3 support and simplified TLS handling (automatic HTTPS via Caddy) are desired
  • the team is ready to check code for global-state freedom and harden it accordingly

FrankenPHP is rather (still) not the right choice when:

  • a large legacy codebase exists whose behavior strongly depends on process isolation between requests
  • operational safety and decades of production maturity are more important than maximum performance
  • there is no capacity to actively check and test the code for worker mode suitability

In many cases, a hybrid approach makes sense: testing FrankenPHP first in a staging environment or for a clearly delineated application part before a complete migration of the production environment takes place.

Conclusion

FrankenPHP exemplifies a larger movement in the PHP server landscape: away from the classic "every request starts anew" model, towards permanently running, booted application instances. The combination of integrated Go-based web server, native HTTP/3, and worker mode makes FrankenPHP one of the most exciting options when it comes to PHP performance beyond classic caching strategies.

At the same time, the migration is not a pure configuration switch. Anyone who switches from PHP-FPM to FrankenPHP implicitly also changes their mental model of application architecture, away from stateless isolation, towards consciously managed, long-lived state. For teams that approach this step with the necessary technical care, FrankenPHP in combination with Laravel Octane offers very convincing performance potential for modern PHP applications. Would you like to know whether the migration to FrankenPHP is worthwhile for your project? Arrange a free initial consultation with our team.