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.

Open Source Tech Hub
Open Source Tech Hub
Open Source Tech Hub
From CGI to FrankenPHP: Modern PHP Runtimes That Supercharge Performance

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.

Apache + mod_php request handling
Apache + mod_php request handling

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).

Nginx + PHP‑FPM request handling
Nginx + PHP‑FPM request handling

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.

Swoole architecture
Swoole architecture

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.

RoadRunner worker mode request handling
RoadRunner worker mode request handling

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.

FrankenPHP worker mode with Caddy
FrankenPHP worker mode with Caddy

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.

FrankenPHP classic mode with Caddy
FrankenPHP classic mode with Caddy

Comparison Overview

Comparison table FrankenPHP vs RoadRunner
Comparison table FrankenPHP vs RoadRunner
Differences table FrankenPHP vs RoadRunner
Differences table FrankenPHP vs RoadRunner
Conclusion: FrankenPHP demonstrates significant performance benefits and is a viable direction for modern PHP deployments.
backendPerformancePHPShared MemorySwooleFrankenPHPRoadRunner
Open Source Tech Hub
Written by

Open Source Tech Hub

Sharing cutting-edge internet technologies and practical AI resources.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.