Blog banner

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…

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.
Photo by Ben Griffiths on Unsplash

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:

Announcement

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 to frankenphp_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

Version 1.0.1