PHP-FPM has been the standard PHP execution model since PHP 5.3. FrankenPHP arrived in 2022 and offers a fundamentally different architecture: a Go-based application server built on Caddy that can run PHP in persistent worker mode. In 2026, both are mature options, but they solve different problems and impose different constraints on your application code.
This article breaks down the architectural differences, real-world performance characteristics, and practical trade-offs for teams running legacy PHP applications who are evaluating whether FrankenPHP is the right move.
Architecture Overview
PHP-FPM: Process Manager with Share-Nothing Execution
PHP-FPM (FastCGI Process Manager) manages a pool of PHP worker processes. Each request is assigned to a worker, which executes the PHP script from the top, processes the request, sends the response, and resets. The key architectural properties are:
- Share-nothing: Every request starts with a clean process state. No data leaks between requests.
- Process-per-request: Each concurrent request needs a process. The pool size limits concurrency.
- Web server independence: PHP-FPM runs behind Nginx, Apache, or Caddy via FastCGI protocol.
- Stateless by design: The application boots and tears down on every request.
FrankenPHP: Embedded PHP with Persistent Workers
FrankenPHP embeds PHP directly into the Caddy web server. It supports two modes:
Standard mode works like PHP-FPM: a new PHP execution context per request. This provides compatibility with existing code while gaining Caddy’s features.
Worker mode is the transformative option. The PHP application boots once and handles multiple requests in a persistent process. The key properties are:
- Persistent state: The application framework (autoloader, configuration, routes, service container) stays in memory.
- Single binary: Caddy plus PHP in one executable, with no separate PHP-FPM service to manage.
- Built-in HTTP/2 and HTTP/3: Caddy handles TLS termination and modern protocol support natively.
- Early hints (103): FrankenPHP supports HTTP 103 Early Hints for preloading assets.
Performance Comparison
Bootstrap Elimination
The biggest performance difference comes from worker mode eliminating the per-request bootstrap. For a Laminas MVC application with 40 modules and 200 service definitions:
| Metric | PHP-FPM | FrankenPHP Worker |
|---|---|---|
| Bootstrap time per request | 35-80ms | 0ms (amortised) |
| Average response time (API) | 85ms | 25ms |
| Time to first byte (TTFB) | 90ms | 30ms |
| Requests per second (single worker) | ~12 | ~40 |
These numbers are representative, not benchmarks. Your application’s bootstrap weight determines the actual gain.
Memory Usage
PHP-FPM’s memory profile is predictable: each worker allocates memory for the full application on each request, then frees it. Peak memory per worker is your application’s full memory footprint.
FrankenPHP worker mode’s memory profile is different: baseline memory is higher (the full application is loaded permanently), but incremental memory per request is lower (only request-specific allocations). Over many concurrent requests, total memory can be lower than the equivalent PHP-FPM pool.
However, memory leaks in worker mode accumulate. PHP-FPM’s request teardown is a natural garbage collector. Worker mode requires disciplined memory management.
OPcache Considerations
With PHP-FPM, OPcache stores compiled bytecode in shared memory. The first request to each PHP file is slower (compilation), and subsequent requests are fast.
With FrankenPHP worker mode, the application code is compiled and loaded once at startup. OPcache still helps for any dynamically included files, but the primary codebase is already resident in the worker’s memory.
If you have already applied the OPcache optimisations described in the PHP Performance Playbook, the marginal gain from worker mode is smaller (you have already eliminated the compilation cost; worker mode eliminates the loading-and-initialisation cost).
Compatibility Requirements
PHP-FPM: Works with Everything
PHP-FPM’s share-nothing model means virtually all PHP code works correctly. Static properties, global variables, singleton patterns, superglobal access, and even poorly structured code all function correctly because each request starts clean.
This is why PHP-FPM remains the safe default for legacy applications.
FrankenPHP Standard Mode: Mostly Compatible
Standard mode (non-worker) is highly compatible with existing code. The main differences are in the web server layer (Caddy instead of Nginx or Apache), not the PHP execution model.
Watch for:
.htaccessrules that need translating to Caddy’s configuration format- Apache-specific environment variables
mod_rewriterules that assume Apache
FrankenPHP Worker Mode: Strict Requirements
Worker mode requires your application to handle persistent state correctly. Code that relies on PHP’s share-nothing model will break. The FrankenPHP Workers for Legacy PHP guide covers the specific compatibility issues in detail, but the major categories are:
- Static properties that accumulate data across requests
- Singleton instances that cache request-specific data
- Global and superglobal variables that are not reset
- Memory leaks from event listeners, closures, or circular references
- Database connections with accumulated session state
Deployment Model Differences
PHP-FPM Deployment
A typical PHP-FPM deployment involves:
- A web server (Nginx or Apache) handling static files and TLS
- PHP-FPM running as a separate service
- Configuration for both services
- Process management (systemd units for each)
- Deployment via code push plus
php-fpm reload
This is well-understood, well-documented, and supported by every hosting provider and deployment tool.
FrankenPHP Deployment
FrankenPHP simplifies the operational stack:
- Single binary (or Docker image) for web server plus PHP
- One configuration file (Caddyfile or JSON)
- One process to manage
- Built-in automatic HTTPS via Let’s Encrypt
The trade-off is maturity. PHP-FPM has over a decade of production experience, extensive monitoring tooling, and broad operational knowledge in the PHP community. FrankenPHP is newer, with a smaller ecosystem of monitoring and debugging tools.
Decision Framework
Choose PHP-FPM When
- Your codebase uses extensive global state, singletons, or static accumulators
- You rely on Apache-specific features like
.htaccessandmod_rewrite - Your team has deep operational experience with PHP-FPM and Nginx or Apache
- You need maximum compatibility with third-party libraries
- Your application has long-running requests that would block workers
- You are running on shared hosting or managed PHP platforms
The PHP-FPM Tuning by Measurement guide covers how to extract maximum performance from this model.
Choose FrankenPHP Standard Mode When
- You want Caddy’s features (automatic HTTPS, HTTP/3, Early Hints) without changing your PHP execution model
- You are modernising your deployment pipeline and want a simpler stack
- You plan to migrate to worker mode later and want to start with the server transition first
Choose FrankenPHP Worker Mode When
- Your application has a heavy bootstrap phase (large framework, many modules)
- Your codebase is already reasonably modern: dependency injection, no singletons, clean separation of request state
- You are building a new application or have already refactored away from global state patterns
- Latency is a primary concern and you have measured that bootstrap cost is a significant portion of response time
Migration Path
For legacy applications, the practical migration path is incremental:
- Optimise PHP-FPM first. Apply OPcache tuning, preloading, realpath cache, and pool configuration. This captures the easy performance wins.
- Try FrankenPHP standard mode. Replace Nginx or Apache with Caddy and FrankenPHP, but keep the per-request execution model. Validate compatibility.
- Audit for worker-mode compatibility. Search for global state, singletons, and memory leak patterns. Estimate the refactoring effort.
- Pilot worker mode on a non-critical endpoint. Run one route through worker mode while keeping the rest on standard mode. Measure the performance difference and monitor for state leaks.
- Expand worker mode coverage once you have confidence in the compatibility work.
Skipping directly to step 5 is tempting, but legacy codebases almost always have hidden state dependencies that surface under worker mode. The incremental approach reduces risk.
The Bigger Picture
The PHP execution model is evolving. PHP-FPM’s share-nothing architecture has been PHP’s defining characteristic for over a decade. FrankenPHP, RoadRunner, and Swoole all challenge this model by offering persistent application state.
For legacy applications, the question is not “which is faster?” but “what is the total cost of adoption?” FrankenPHP worker mode is faster for applications with heavy bootstraps. PHP-FPM is cheaper to adopt and operates safely with any codebase. Your choice depends on where your application sits on the spectrum between “legacy codebase we cannot easily refactor” and “modern application with clean state management.”
The performance chapter in the Zend Framework book covers the foundational principles. The PHP-FPM Tuning by Measurement guide gives you the PHP-FPM-specific playbook. The FrankenPHP Workers for Legacy PHP guide gives you the worker-mode playbook. Start with measurement, then choose the execution model that matches your constraints.