FrankenPHP è un server applicativo per PHP basato su Caddy. Unisce web server e motore PHP in un unico processo con HTTP/2 e HTTP/3, supporto automatico per HTTPS, hot reloading, modalità worker (in pratica con app persistente in memoria), Early Hints e integrazioni per Laravel e Symfony. Rispetto agli stack tradizionali, garantisce performance superiori (fino a 3,5 volte più veloce di PHP-FPM) e permette un deploy semplificato tramite le immagini Docker ufficiali.
Architettura e funzionalità di FrankenPHP
FrankenPHP incorpora l'interprete PHP in un'applicazione Go via CGO, ed esegue ogni richiesta PHP su thread POSIX reali grazie a PHP-ZTS. Questi ultimi, a loro volta, vengono controllati da uno stato macchina basato su Go e C. Il risultato è un unico binario che prende il nome di "frankenphp" e sostituisce web server come Nginx e Apache nonché PHP-FPM.
Nello specifico abbiamo:
- la modalità Classic che esegue ogni richiesta come tradizionale, quindi un thread per script;
- una modalità Worker che mantiene l'app PHP in memoria tra le richieste in modo da ridurre l'overhead di avvio;
- il supporto nativo a HTTP/2 e HTTP/3 con certificati TLS di Let's Encrypt e ZeroSSL gestiti automaticamente da Caddy;
- un unico server PHP che implementa il supporto a HTTP 103 Early Hints e può migliorare il caricamento del sito web fino al 30%;
- un hub Mercure interno per l'invio di eventi push al client web in tempo reale;
- l'Hot reloading con ricarica automatica del codice in sviluppo con Mercure;
- logging JSON ed esportazione di metriche per il monitoraggio.
FrankenPHP è inoltre compatibile con PHP 8.2 o versione successiva e la maggior parte delle estensioni del linguaggio tra cui anche OPCache e XDebug. Supporta inoltre moduli Caddy aggiuntivi e può essere utilizzato per generare dei binari statici standalone delle app PHP.
Tutto ciò è possibile con un'unica applicazione, senza la necessità di ricorrere a servizi esterni.
Prestazioni, deployment e integrazione
In modalità worker ed eseguendo un'app Symfony, FrankenPHP risulta circa 3,5 volte più veloce di PHP-FPM. L'uso di HTTP/2, HTTP/3 ed Early Hints accelera ulteriormente il caricamento. Per massimizzare le performance va poi ottimizzata la configurazione. Ad esempio, il numero di thread, che è pari a 2×CPU come impostazione predefinita, va tarato e si può abilitare la direttiva max_threads auto per una scalabilità dinamica simile a pm.dynamic di PHP-FPM.
In fase di produzione è consigliabile usare delle build basate su glibc, si pensi per esempio alle immagini Debian, in quanto la variante musl tende a rallentare PHP-ZTS.
FrankenPHP usa un Caddyfile per la configurazione del server:
caddyfile
unsitoqualsiasi.com {
root * /var/www/app/public
php_server
}
Qui php_server gestisce il routing PHP, la consegna di file statici e il comportamento equivalente a try_file. Esistono inoltre immagini Docker ufficiali per PHP 8.2 o 8.5. Con Docker Compose si può sostituire facilmente la coppia Nginx + PHP-FPM con un unico servizio montando dei volumi per i certificati e la configurazione.
In produzione è consigliabile esporre le porte 80 o 443 e usare SERVER_NAME per il dominio, FrankenPHP genera e rinnova automaticamente i certificati. Supporta Kubernetes e dispone di una modalità stand-alone sotto forma di binario statico auto-eseguibile.
Sicurezza, ambito di applicazione e limitazioni
Caddy gestisce HTTPS nativamente e genera certificati con localhost in sviluppo e dominio in produzione. In container si può eseguire come utente non-root assegnando il parametro CAP_NET_BIND_SERVICE al binario per il binding alle porte privilegiate. Per ambienti ad alta sicurezza sono poi disponibili immagini basate su container minimal.
Attenzione alle estensioni PHP, alcune infatti non sono thread-safe e non funzionano come imap e newrelic. Inoltre la versione statica con musl non supporta alcune funzionalità tra cui GLOB_BRACE. FrankenPHP non è vulnerabile a rischi particolari rispetto a PHP o Caddy, ma va considerata la complessità aggiunta dall'esecuzione concorrente su thread.
FrankenPHP è ideale per applicazioni web e API sviluppate con framework che possono sfruttare la modalità worker. È adatto anche a microservizi real-time, grazie al supporto Mercure, o a semplici siti web, grazie al deployment one-file. Permette inoltre di creare applicazioni CLI PHP eseguibili come servizi standalone.
Le applicazioni stateful o con callback su exit() potrebbero non essere però compatibili con worker mode in quanto è necessario prevenire le perdite di stato. Alcune funzionalità PHP non sono disponibili in FrankenPHP e la persistenza in memoria impone di gestire manualmente la pulizia della stessa. Ad esempio, dopo aver avviato automaticamente il server HTTPS su localhost con compressione e PHP:
caddyfile
localhost {
encode zstd br gzip
php_server
}
si può procedere in questo modo con un'auto-esecuzione:
require __DIR__.'/vendor/autoload.php';
$app = new \App\Kernel();
$app->boot();
$handler = function() use ($app) {
echo $app->handle($_GET, $_POST, $_COOKIE, $_FILES, $_SERVER);
};
for ($i = 0; true; ++$i) {
$keep = \frankenphp_handle_request($handler);
$app->terminate();
gc_collect_cycles();
if (!$keep) break;
}
$app->shutdown();
In questo modo il loop mantiene l'app in memoria e gestisce le richieste successive in millisecondi.