FrankenPHP: Modern PHP App Server Built on Caddy
What FrankenPHP is
FrankenPHP is an application server for PHP built on top of the Caddy web server. Instead of the classic split between a web server such as Nginx and a separate PHP-FPM process pool, FrankenPHP embeds the PHP interpreter directly inside a Go program and serves requests from a single binary. The README describes it as a modern application server that works with any PHP app, and it lists Early Hints, a worker mode, real-time capabilities, hot reloading, automatic HTTPS, HTTP/2 and HTTP/3 among its features.
It targets PHP developers and the people who operate PHP in production. The README calls out Laravel and Symfony specifically: both have official integrations with FrankenPHP's worker mode, which keeps an application booted in memory between requests. FrankenPHP can also be used as a standalone Go library, so a Go program that speaks net/http can embed PHP and hand requests to it.
The project lives under the php organization on GitHub, next to the PHP interpreter itself, and has been developed in the open since 2022. It is written in Go (with a C layer that talks to PHP) and released under the MIT licence.
How it works
The internals document in the repository explains the architecture. FrankenPHP embeds the PHP interpreter into Go via CGO. Each PHP execution runs on a real POSIX thread rather than a goroutine, because PHP's ZTS (Zend Thread Safety) model requires it. Go orchestrates those threads through a state machine, while a C layer implements the PHP SAPI lifecycle: script execution, superglobal management and request context.
There are three kinds of threads. A main PHP thread starts first: it applies php.ini overrides, snapshots the environment for per-thread sandboxing, starts the SAPI module and stays alive for the life of the server. Regular threads handle the classic model, where every request runs a PHP script from the top. Worker threads keep one PHP script alive across many requests: the script calls frankenphp_handle_request() in a loop, the call blocks in Go until a request arrives, the PHP callback handles it, the request context is cleaned up and the loop continues.
Each thread has a lifecycle state (reserved, booting, inactive, ready, shutting down, restarting, rebooting and so on), and the Go side uses those states to boot threads on demand, restart workers when files change or an admin calls the restart endpoint, and fully recycle a thread's ZTS state after a configured number of requests. The Go layer also contains the thread pool, request routing and auto-scaling code. Because everything is inside Caddy, HTTPS certificates, HTTP/3 and the rest of Caddy's configuration model come with it.
Key features
- Classic mode: runs existing PHP applications the way PHP-FPM would, one script execution per request, with a documented migration path from Nginx and PHP-FPM.
- Worker mode: boot the application once and keep it in memory; the docs say requests are then handled in a few milliseconds. By default two workers per CPU are started, and the count is configurable.
- Caddy underneath: automatic HTTPS, HTTP/2 and HTTP/3, configured through a Caddyfile or environment variables such as
SERVER_NAME. - Early Hints: support for the 103 HTTP status code so browsers can start fetching assets before the response is ready.
- Real-time: a built-in Mercure integration for pushing updates to clients.
- Hot reloading and file watching: the
--watchoption restarts workers when matching files change. - Efficient large static files via X-Sendfile style handling.
- Standalone apps and static binaries: package a PHP application into a single self-executable binary, or build fully static FrankenPHP binaries.
- PHP extensions written in Go: the docs include a guide to writing PHP extensions in Go.
- Observability, logging and a Go library mode, plus integrations documented for Symfony, Laravel and WordPress.
Getting started
On Linux and macOS the README offers an install script that picks the right build for the platform:
curl https://frankenphp.dev/install.sh | sh
Standalone binaries are published for Linux, macOS (Apple Silicon) and Windows and contain PHP 8.5; the Linux binaries are statically linked so they run on any distribution without extra dependencies. Maintainers also publish rpm, deb and apk packages (PHP 8.2 to 8.5 are available), and there is a Homebrew package:
brew install dunglas/frankenphp/frankenphp
To serve the current directory, or to run a command-line script:
frankenphp php-server
frankenphp php-cli /path/to/your/script.php
With Docker, mount the app into the official image and browse to https://localhost (the README warns against https://127.0.0.1, which needs extra setup):
docker run -v .:/app/public \
-p 80:80 -p 443:443 -p 443:443/udp \
dunglas/frankenphp
Worker mode with the standalone binary is one flag away:
frankenphp php-server --worker /path/to/your/worker/script.php
Symfony and Laravel (through Laravel Octane) have their own worker-mode guides. For a custom application, the worker docs show a script that boots the kernel once and then loops on frankenphp_handle_request(), honouring a MAX_REQUESTS environment variable so leaky code can be restarted periodically. With the Caddy admin API enabled, all workers can be restarted gracefully:
curl -X POST http://localhost:2019/frankenphp/workers/restart
Use cases
- Replacing an Nginx plus PHP-FPM pair with one process and one config, while getting automatic HTTPS.
- Running Symfony or Laravel in worker mode to remove per-request bootstrap cost for API and web workloads.
- Shipping a PHP application as a single self-contained binary, for example a CLI tool or an on-premise app.
- Serving PHP from a Go service by embedding FrankenPHP as a library behind
net/http. - Adding real-time updates (Mercure) or 103 Early Hints without bolting on another server.
- Local development with hot reloading and file watching against the same server used in production.
How it compares
The obvious comparison is the traditional PHP stack of Nginx or Apache in front of PHP-FPM, which the docs address with a migration guide. FrankenPHP collapses those layers into one process; the trade-off is that all requests share one process's threads instead of a pool of separate child processes (see below). For long-running PHP applications, worker mode sits in the same space as Laravel Octane's other server back ends and similar persistent-process runtimes; FrankenPHP is one of the servers Octane supports rather than a replacement for it. Treat performance differences as workload-specific: the project links a Symfony demo app with benchmarks rather than making blanket claims in the README.
Things to know before adopting
- Thread-safety matters: FrankenPHP needs a thread-safe (ZTS) PHP. The known-issues page lists imap, newrelic and pcov as incompatible because they are not thread-safe, and reports problems with the datadog profiler, beta-only Blackfire support and imagick, whose OpenMP threads conflict with FrankenPHP's (mitigations are documented).
- One process, shared fate: if a script allocates past PHP's hard memory limit, PHP exits the process. PHP-FPM loses one child; FrankenPHP loses every in-flight request and stops until restarted, so the docs recommend running it under a supervisor (Docker restart policy or systemd
Restart=, which the deb and rpm packages already set). - Worker mode changes assumptions: application state persists between requests, memory leaks accumulate, and
set_exception_handleronly fires when the worker script ends. Plan forMAX_REQUESTS-style recycling. - musl builds: the static binary and Alpine images use musl libc, which lacks some features such as
GLOB_BRACE; the docs suggest the GNU variant or Debian-based images if you hit issues. - Composer
@phpscripts do not work out of the box with the FrankenPHP binary; the docs provide a wrapper-script workaround. - Licence: MIT, so there are no copyleft obligations for embedding or redistributing it.
Project activity
As of October 2026 the repository has about 11,400 stars and roughly 500 forks. It was created in March 2022, is written in Go, is licensed under MIT, and was last pushed in October 2026. Documentation, including the worker, configuration, production and known-issues guides, is at frankenphp.dev; the source is at github.com/php/frankenphp, and the Go API is documented on pkg.go.dev.
Enjoying this project?
Discover more amazing open-source projects on TechLogHub. We curate the best developer tools and projects.
Repository:https://github.com/php/frankenphp
GitHub - php/frankenphp: FrankenPHP: Modern PHP App Server Built on Caddy
FrankenPHP is an MIT-licensed PHP application server built on Caddy and written in Go, with worker mode for Laravel and Symfony, automatic HTTPS, HTTP/3, Early ...
github - php/frankenphp
Related Projects
FastAPI: Python Web Framework for Building APIs
Python API framework using type hints for validation, dependency injection and OpenAPI docs.
Gin: High-Performance HTTP Web Framework for Go
Fast Go HTTP framework with a zero-allocation router, middleware, binding and validation.
Caddy: Web Server With Automatic HTTPS
Go web server and reverse proxy with automatic HTTPS, a simple Caddyfile and a live JSON config API.
Maintain FrankenPHP: Modern PHP App Server Built on Caddy? Add the badge to your README
Show that the project is featured on TechLogHub and send readers to this write-up.
[](https://techloghub.com/open-source/frankenphp?utm_source=techloghub_badge&utm_medium=referral&utm_campaign=readme)
