From CGI to FrankenPHP: Modern PHP Runtimes That Supercharge Performance
This article traces the evolution of PHP request handling from early CGI and mod_php through PHP‑FPM to modern runtimes like Swoole, RoadRunner, and FrankenPHP, explaining their architectures, shared‑memory models, performance gains, and practical migration tips.
Evolution of PHP Request Handling
Early PHP deployments used the CGI model, spawning a new OS process for every HTTP request. This caused high process‑creation overhead and prevented sharing of resources such as persistent database connections.
To reduce this cost the PHP team introduced mod_php , which embeds the interpreter inside Apache worker processes. The same process handles multiple requests, allowing connection pooling and eliminating the per‑request fork.
mod_php still allocated the same memory for static assets and PHP code, limiting efficiency. Starting with PHP 5.3.3 the official PHP‑FPM (FastCGI Process Manager) became the default. PHP‑FPM runs a dedicated pool of PHP workers, reduces memory usage, speeds execution, and works with any web server (commonly Nginx).
Modern PHP Runtimes
Swoole provides an asynchronous, event‑driven engine inspired by Node.js. It runs as a CLI server (e.g. php -f server.php) that launches a configurable number of worker processes listening on a TCP port. Requests are received, an event loop dispatches them, and responses are sent back. Swoole requires code changes because the application must be written for a non‑blocking environment.
RoadRunner is a Go‑based application server that mimics the PHP‑FPM model but keeps a pool of native PHP processes alive and communicates via sockets. It also offers built‑in middleware, gRPC, queues, caching and real‑time messaging.
Both Swoole and RoadRunner rely on a shared‑memory model : a long‑lived worker pool handles many requests without restarting the interpreter, dramatically reducing per‑request overhead.
FrankenPHP: A Go‑Based PHP Server
FrankenPHP (released 2022, production‑ready Oct 2023) is built in Go and runs on top of the Caddy web server, which provides HTTP/1‑3, automatic HTTPS and extensibility. It compiles a custom PHP runtime and bridges Caddy to PHP via the function \frankenphp_handle_request(callback).
It supports two operating modes:
Classic mode – a drop‑in replacement for Nginx + PHP‑FPM; no code changes are required.
Worker mode – similar to RoadRunner; Caddy creates a pool of PHP workers that pause at \frankenphp_handle_request until a request arrives.
During a request the worker executes the PHP script until it calls \frankenphp_handle_request. Execution pauses, Caddy processes the HTTP request, and the response is sent back. After the response the script resumes until it ends or another request triggers the callback again. Super‑globals ( $_GET, $_POST, $_SERVER) are populated with the current request inside the callback and retain the last request’s values after the callback returns.
Initialize default values before the first callback if needed.
Create request‑wrapper objects inside the callback, not globally.
Shared‑Memory Model Considerations
Traditional PHP uses a “no‑shared” model: each request starts a fresh interpreter, discarding all state (except opcache, APCu and persistent DB connections). The shared‑memory model keeps the same process alive across requests, so any in‑memory state persists unless explicitly reset.
When using dependency‑injection containers, services that depend on request‑specific data must be re‑instantiated per request. Global variables, singletons, or storing the container itself in object properties are discouraged.
Framework support:
Laravel Octane works with Swoole, RoadRunner and FrankenPHP.
Symfony Runtime (PSR‑7/15) supports all three runtimes and provides Symfony\Contracts\Service\ResetInterface to reset services between requests.
WordPress cannot currently run in shared‑memory mode; a community effort ( https://github.com/WordPress-PSR/swoole) aims to wrap it in a PSR‑15 layer.
Performance Impact
In a minimal benchmark (no framework) FrankenPHP worker mode reduced average request time from ~300 ms to ~30 ms (≈10× speedup). Classic mode gave a ~50 % improvement (300 ms → 180 ms) with zero code changes. The gains stem from eliminating process spawning and FastCGI communication overhead.
Comparison Overview
Conclusion: FrankenPHP demonstrates significant performance benefits and is a viable direction for modern PHP deployments.
Open Source Tech Hub
Sharing cutting-edge internet technologies and practical AI resources.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
