FrankenPHP replaces both your web server (Nginx, Apache) and PHP-FPM with a single binary. This guide covers a basic migration for a typical PHP application.
| PHP-FPM setup | FrankenPHP equivalent |
|---|---|
| Nginx/Apache + PHP-FPM | Single frankenphp binary |
php-fpm.conf pool settings | frankenphp global option |
Nginx server {} block | Caddyfile site block |
php_value / php_admin_value | php_ini Caddyfile directive |
pm = static / pm.max_children | num_threads |
pm = dynamic | max_threads auto |
A typical Nginx + PHP-FPM configuration:
server {
listen 80;
server_name example.com;
root /var/www/app/public;
index index.php;
location / {
try_files $uri $uri/ /index.php$is_args$args;
}
location ~ \.php$ {
fastcgi_pass unix:/run/php/php-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
}
Becomes a single Caddyfile:
example.com {
root /var/www/app/public
php_server
}
That’s it. The php_server directive handles PHP routing, try_files-like behavior, and static file serving.
Your existing php.ini works as-is. See Configuration for where to place it depending on your installation method.
You can also set directives directly in the Caddyfile:
{
frankenphp {
php_ini memory_limit 256M
php_ini max_execution_time 30
}
}
example.com {
root /var/www/app/public
php_server
}
In PHP-FPM, you tune pm.max_children to control the number of worker processes.
In FrankenPHP, the equivalent is num_threads:
{
frankenphp {
num_threads 16
}
}
By default, FrankenPHP starts 2 threads per CPU. For dynamic scaling similar to PHP-FPM’s pm = dynamic:
{
frankenphp {
num_threads 4
max_threads auto
}
}
A typical PHP-FPM Docker setup using two containers (Nginx + PHP-FPM) can be replaced by a single container:
Before:
services:
nginx:
image: nginx:1
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf
- .:/var/www/app
ports:
- "80:80"
php:
image: php:8.5-fpm
volumes:
- .:/var/www/app
After:
services:
php:
image: dunglas/frankenphp:1-php8.5
volumes:
- .:/app/public
- caddy_data:/data
- caddy_config:/config
ports:
- "80:80"
- "443:443"
- "443:443/udp"
volumes:
caddy_data:
caddy_config:
If you need additional PHP extensions, see Building Custom Docker Image.
For framework-specific Docker setups, see Symfony Docker and Laravel.
In classic mode, FrankenPHP works like PHP-FPM: each request boots the application from scratch. This is a safe starting point for migration.
For better performance, you can switch to worker mode, which boots your application once and keeps it in memory:
example.com {
root /var/www/app/public
php_server {
root /var/www/app/public
worker index.php 4
}
}
Caution
Worker mode keeps your application in memory between requests. Make sure your code does not rely on global state being reset between requests. Frameworks like Symfony, Laravel, and API Platform have native support for this mode.
After migrating, you no longer need:
php-fpm service/process)