FrankenPHP: The New Star in the PHP Ecosystem
FrankenPHP: The New Star in the PHP Ecosystem
FrankenPHP is an alternative Server API (SAPI) for PHP that is built on the Caddy web server, and includes a number of high-performance configuration options. It’s already directly supported by multiple major frameworks in the PHP ecosystem.
Table Of Content
- What is FrankenPHP?
- Quick Start
- Making a conclusion
What is FrankenPHP?
FrankenPHP is a modern application server for PHP built on top of the Caddy web server.
FrankenPHP gives superpowers to your PHP apps thanks to its stunning features: Early Hints, worker mode, real-time capabilities, automatic HTTPS, HTTP/2, and HTTP/3 support…
FrankenPHP works with any PHP app and makes your Laravel and Symfony projects faster than ever thanks to their official integrations with the worker mode.
FrankenPHP can also be used as a standalone Go
library to embed PHP in any app using net/http.
Learn more on frankenphp.dev and in this slide deck:
Today I released FrankenPHP during the ForumPHP conference. FrankenPHP is a new app server for PHP apps (built on top…dunglas.dev
Announcement
externals - Opening PHP's #internals to the outsideexternals.io
Hello,
The PHP Foundation would like to announce that it has decided to directly
support the FrankenPHP project moving forward. The plan is that FrankenPHP
will move to the PHP GitHub organization, which will make it easier for
core contributors and others to be actively involved in the project. A
portion of its documentation will also move to php.net (exact details TBD).
For those not familiar with it, FrankenPHP is an alternative Server API
(SAPI) for PHP that is built on the Caddy web server, and includes a number
of high-performance configuration options. It’s already directly supported
by multiple major frameworks in the PHP ecosystem.
This change will put FrankenPHP in essentially the same administrative
“bucket” as PECL extensions. Its governance will not change; the same
maintainers will still be maintaining it, but it will now be easier for
others to contribute to the project and coordinate larger efforts.
At this time, there are no plans for tighter integration into php-src the
way PHP-FPM or the CLI SAPI are. Should there be serious discussion of
that in the future, it will involve an RFC, just like any other major
addition to php-src.
Kind Regards,
Jakub
What is Mean ?
This is a big step forward for PHP. FrankenPHP offers modern capabilities PHP has long needed. With official support, it could become a go-to choice for high-performance PHP hosting — especially for those who want something more modern than PHP-FPM.
Quick Start
Here’s a simple Hello World example using FrankenPHP. Since FrankenPHP is a modern application server, it runs PHP apps differently than traditional servers like Apache or Nginx + PHP-FPM.
1. Prerequisites
Make sure you have:
- Docker installed (FrankenPHP is easiest to run via Docker)
- Basic PHP knowledge
2. Directory Structure
Create a directory for your project:
frankenphp-hello/
├── Dockerfile
├── Caddyfile
└── index.php
3. Files
index.php
<?php
echo "Hello, World from FrankenPHP!";
Caddyfile
:80
root * /app
php_auto_sessions
php_server index.php
file_server
DockerFile
FROM dunglas/frankenphp
COPY . /app
Run it with Docker
docker build -t frankenphp-hello .
docker run -p 8080:80 frankenphp-hello
Open your browser and go to: 👉 http://localhost:8080
Also FrankenPHP support modern framework such as Laravel or Symfony.
Working Modes
FrankenPHP works 2 mode Classic Mode and Worker Mode
Classic Mode
Without any additional configuration, FrankenPHP operates in classic mode. In this mode, FrankenPHP functions like a traditional PHP server, directly serving PHP files. This makes it a seamless drop-in replacement for PHP-FPM or Apache with mod_php.
Similar to Caddy, FrankenPHP accepts an unlimited number of connections and uses a fixed number of threads to serve them. The number of accepted and queued connections is limited only by the available system resources. The PHP thread pool operates with a fixed number of threads initialized at startup, comparable to the static mode of PHP-FPM. It’s also possible to let threads scale automatically at runtime, similar to the dynamic mode of PHP-FPM.
Queued connections will wait indefinitely until
a PHP thread is available to serve them. To prevent that, you can use the max_wait_time configuration to limit how long a request may wait for a free PHP
thread before being rejected. Additionally, you can set a reasonable write timeout in Caddy.
Each Caddy instance will only spin up one
FrankenPHP thread pool, which will be shared across all php_server blocks.
Using FrankenPHP Workers Mode
Boot your application once and keep it in memory. FrankenPHP will handle incoming requests in a few milliseconds.
Starting Worker Scripts
Docker
Set the value of the FRANKENPHP_CONFIG environment variable to worker /path/to/your/worker/script.php:
docker run \
-e FRANKENPHP_CONFIG="worker /app/path/to/your/worker/script.php" \
-v $PWD:/app \
-p 80:80 -p 443:443 -p 443:443/udp \
dunglas/frankenphp
Standalone Binary
Use the --worker option of the php-server command to serve the content of the current
directory using a worker:
frankenphp php-server --worker /path/to/your/worker/script.php
If your PHP app is embedded in the binary, you
can add a custom Caddyfile in the root directory of the
app. It will be used automatically.
It’s also possible to restart the worker on file
changes with the --watch option. The following
command will trigger a restart if any file ending in .php
in the /path/to/your/app/ directory or subdirectories is
modified:
frankenphp php-server --worker /path/to/your/worker/script.php --watch "/path/to/your/app/**/*.php"
Symfony Runtime
The worker mode of FrankenPHP is supported by the Symfony Runtime Component. To start any Symfony application in a worker, install the FrankenPHP package of PHP Runtime:
composer require runtime/frankenphp-symfony
Start your app server by defining the APP_RUNTIME environment variable to use the FrankenPHP
Symfony Runtime:
docker run \
-e FRANKENPHP_CONFIG="worker ./public/index.php" \
-e APP_RUNTIME=Runtime\\FrankenPhpSymfony\\Runtime \
-v $PWD:/app \
-p 80:80 -p 443:443 -p 443:443/udp \
dunglas/frankenphp
Laravel Octane
See the dedicated documentation.
Custom Apps
The following example shows how to create your own worker script without relying on a third-party library:
<?php
// public/index.php
// Prevent worker script termination when a client connection is interrupted
ignore_user_abort(true);
// Boot your app
require __DIR__.'/vendor/autoload.php';
$myApp = new \App\Kernel();
$myApp->boot();
// Handler outside the loop for better performance (doing less work)
$handler = static function () use ($myApp) {
// Called when a request is received,
// superglobals, php://input and the like are reset
echo $myApp->handle($_GET, $_POST, $_COOKIE, $_FILES, $_SERVER);
};
$maxRequests = (int)($_SERVER['MAX_REQUESTS'] ?? 0);
for ($nbRequests = 0; !$maxRequests || $nbRequests < $maxRequests; ++$nbRequests) {
$keepRunning = \frankenphp_handle_request($handler);
// Do something after sending the HTTP response
$myApp->terminate();
// Call the garbage collector to reduce the chances of it being triggered in the middle of a page generation
gc_collect_cycles();
if (!$keepRunning) break;
}
// Cleanup
$myApp->shutdown();
hen, start your app and use the FRANKENPHP_CONFIG environment variable to configure your
worker:
docker run \
-e FRANKENPHP_CONFIG="worker ./public/index.php" \
-v $PWD:/app \
-p 80:80 -p 443:443 -p 443:443/udp \
dunglas/frankenphp
By default, 2 workers per CPU are started. You can also configure the number of workers to start:
docker run \
-e FRANKENPHP_CONFIG="worker ./public/index.php 42" \
-v $PWD:/app \
-p 80:80 -p 443:443 -p 443:443/udp \
dunglas/frankenphp
Restart the Worker After a Certain Number of Requests
As PHP was not originally designed for long-running processes, there are still many libraries and legacy codes that leak memory. A workaround to using this type of code in worker mode is to restart the worker script after processing a certain number of requests:
The previous worker snippet allows configuring a
maximum number of request to handle by setting an environment variable named MAX_REQUESTS.
Restart Workers Manually
While it’s possible to restart workers on file changes, it’s also possible to restart all workers gracefully via the Caddy admin API. If the admin is enabled in your Caddyfile, you can ping the restart endpoint with a simple POST request like this:
curl -X POST http://localhost:2019/frankenphp/workers/restart
Worker Failures
If a worker script crashes with a non-zero exit
code, FrankenPHP will restart it with an exponential backoff strategy. If the worker script stays up
longer than the last backoff * 2, it will not penalize the worker script and restart it again. However, if
the worker script continues to fail with a non-zero exit code in a short period of time (for example,
having a typo in a script), FrankenPHP will crash with the error: too many consecutive failures.
Superglobals Behavior
PHP superglobals ($_SERVER, $_ENV, $_GET…) behave as follows:
- before the first call to
frankenphp_handle_request(), superglobals contain values bound to the worker script itself - during and after the call to
frankenphp_handle_request(), superglobals contain values generated from the processed HTTP request, each call tofrankenphp_handle_request()changes the superglobals values
To access the superglobals of the worker script inside the callback, you must copy them and import the copy in the scope of the callback:
<?php
// Copy worker's $_SERVER superglobal before the first call to frankenphp_handle_request()
$workerServer = $_SERVER;
$handler = static function () use ($workerServer) {
var_dump($_SERVER); // Request-bound $_SERVER
var_dump($workerServer); // $_SERVER of the worker script
};
// ...
Making a conclusion
👨👦👦 Leave a comment, I am free for discussion with your any kind technical question.
#FrankenPHP #PHP #Caddy #GoLang #WebServer #HTTP2 #HTTP3 #RealTime #WorkerMode #AutomaticHTTPS #EarlyHints #Laravel #Symfony #OpenSource #SoftwareDevelopment