Concrètement, c'est quoi FrankenPHP ?
Par Louis-Arnaud Catoire
PHP propulse encore aujourd'hui une part considérable du web, porté par des frameworks matures comme Symfony, Laravel et des CMS comme WordPress ou Drupal. Pourtant, son modèle d'exécution historique souffre d'une limitation fondamentale : chaque requête HTTP déclenche un cycle complet d'initialisation, d'exécution et de destruction. FrankenPHP propose de résoudre ce problème structurel en repensant la manière dont PHP s'exécute côté serveur.
Le coût réel du modèle share nothing
Pour comprendre ce que FrankenPHP apporte, il faut d'abord mesurer ce que l'architecture classique coûte. Avec PHP-FPM derrière Nginx ou Apache, chaque requête HTTP suit un cycle invariable :
- Le serveur web reçoit la requête et la transmet à PHP-FPM via le protocole FastCGI
- PHP-FPM alloue un worker, charge l'intégralité du framework, exécute le code applicatif
- La réponse est retournée, le processus est détruit, la mémoire libérée
Ce modèle share nothing garantit une isolation totale entre les requêtes. Un bug, une fuite mémoire ou une exception non rattrapée n'affecte jamais la requête suivante. Cette robustesse a fait ses preuves pendant deux décennies. Mais elle a un prix.
Sur une application Symfony typique, le bootstrap représente entre 30 et 60 % du temps de traitement d'une requête : lecture des fichiers de configuration, construction du conteneur d'injection de dépendances, compilation des routes, initialisation des listeners. OPcache atténue le coût en mettant en cache le bytecode compilé, mais l'instanciation des objets et l'assemblage du graphe de services restent incompressibles à chaque requête.
À cette charge computationnelle s'ajoute une complexité opérationnelle. L'architecture de production classique requiert la coordination de plusieurs services : Nginx ou Apache en reverse proxy, PHP-FPM pour l'exécution, Certbot ou un autre outil pour la gestion des certificats TLS, parfois Varnish pour le cache HTTP. Chaque service apporte son fichier de configuration, ses logs, ses mécanismes de reload et ses propres modes de défaillance.
FrankenPHP : un serveur d'application natif pour PHP
FrankenPHP est un serveur d'application écrit en Go, bâti sur Caddy, qui embarque directement l'interpréteur PHP dans son processus. Il ne s'agit pas d'un simple wrapper autour de PHP-FPM : FrankenPHP utilise CGO pour lier le runtime Go au code C de la Zend Engine, la machine virtuelle de PHP. Cette approche permet au serveur web et à l'interpréteur de coexister dans le même espace mémoire, sans communication inter-processus ni protocole intermédiaire.
Le résultat concret : un unique binaire capable de servir une application PHP avec support natif de HTTP/1.1, HTTP/2 et HTTP/3, génération automatique de certificats TLS via Let's Encrypt ou ZeroSSL, et support du temps réel via le protocole Mercure. FrankenPHP peut déployer nativement Symfony, Laravel, API Platform, WordPress, Drupal ou toute application PHP standard.
Architecture interne et choix techniques
L'intégration Go-PHP via CGO
Le choix de Go comme langage hôte n'est pas anodin. Go offre un modèle de concurrence par goroutines particulièrement adapté à la gestion de connexions HTTP massives, un garbage collector à faible latence, et la capacité de produire des binaires statiques sans dépendances système. L'interpréteur PHP, écrit en C, s'intègre via CGO qui permet les appels bidirectionnels entre Go et C.
Chaque thread d'exécution PHP tourne dans une goroutine Go dédiée. Le scheduler de Go gère l'ordonnancement de ces goroutines sur les threads système disponibles, ce qui permet à FrankenPHP de traiter un grand nombre de requêtes concurrentes avec une empreinte mémoire maîtrisée. Cette architecture élimine le surcoût du protocole FastCGI et les context switches entre les processus Nginx et PHP-FPM.
Caddy comme fondation
En s'appuyant sur Caddy plutôt que de réécrire un serveur HTTP, FrankenPHP hérite d'une stack réseau éprouvée : gestion automatique des certificats TLS avec renouvellement transparent, support OCSP stapling, négociation ALPN pour HTTP/2 et HTTP/3 sur QUIC. La configuration de Caddy via Caddyfile reste disponible pour les cas avancés (rate limiting, redirections, headers de sécurité).
Le mode worker : rupture avec le modèle share nothing
Le mode worker constitue l'apport le plus significatif de FrankenPHP. Dans ce mode, l'application PHP est initialisée une seule fois au démarrage du serveur. Le conteneur de services Symfony ou Laravel, les routes compilées, les fichiers de configuration, les metadata Doctrine restent chargés en mémoire et sont réutilisés pour chaque requête entrante.
Le gain mesuré est substantiel. Les benchmarks montrent des temps de réponse divisés par trois à cinq par rapport à PHP-FPM sur des applications Symfony ou Laravel. Sur une API REST typique, le temps de traitement passe de 15-20 ms à 3-5 ms par requête, le bootstrap n'étant exécuté qu'une seule fois.
Pour activer le mode worker avec Symfony, l'intégration passe par le composant Runtime :
composer require runtime/frankenphp-symfony
APP_RUNTIME=Runtime\FrankenPhpSymfony\Runtime
Symfony Runtime orchestre le cycle de vie du worker et s'assure que les services stateful sont correctement réinitialisés entre les requêtes. Ce point est critique : en mode worker, un service qui conserve un état (une connexion base de données, un compteur, un cache local) persiste entre les requêtes. Les fuites mémoire, négligeables en mode share nothing, deviennent un problème de premier ordre. Il faut auditer les services applicatifs et s'assurer qu'aucun état indésirable ne traverse les frontières de requête.
Early Hints : optimisation du rendu navigateur
Le support natif des Early Hints (code HTTP 103) permet au serveur d'envoyer des indications de préchargement au navigateur avant même que la réponse principale ne soit prête. Pendant que PHP génère le HTML, le navigateur télécharge déjà les feuilles de style et les scripts JavaScript. Sur des pages nécessitant un traitement serveur conséquent, le temps de chargement perçu diminue sensiblement.
$response = new Response();
$response->headers->set('Link', '</style.css>; rel=preload; as=style');
$this->sendEarlyHints($response);
Stratégie de migration depuis PHP-FPM
Évaluer la pertinence
FrankenPHP n'est pas systématiquement le bon choix. Pour une application en maintenance avec peu de trafic, l'architecture PHP-FPM éprouvée reste parfaitement adaptée. FrankenPHP prend tout son sens dans trois contextes : les applications à fort trafic où le gain de performance du mode worker justifie l'investissement, les nouveaux projets où la simplification de la stack réduit le time-to-production, et les architectures conteneurisées où le binaire unique simplifie l'orchestration Kubernetes.
Procéder par étapes
La migration recommandée suit une progression prudente. La première étape consiste à déployer FrankenPHP en mode classique (sans worker) comme remplacement direct de Nginx + PHP-FPM. Ce mode est fonctionnellement équivalent et permet de valider la compatibilité de l'application sans risque. Le Dockerfile se résume à l'essentiel :
FROM dunglas/frankenphp:latest
COPY . /app/public
ENTRYPOINT ["frankenphp", "run", "--config", "/etc/caddy/Caddyfile"]
La deuxième étape active le mode worker sur un environnement de staging. Cette phase exige un travail d'audit des services applicatifs pour identifier ceux qui conservent un état entre les requêtes. Les ORM, les systèmes de cache, les connexions aux services externes doivent être vérifiés.
La troisième étape est le déploiement en production avec monitoring renforcé. Les images Docker officielles de FrankenPHP supportent les health checks natifs de Caddy, le graceful shutdown pour les rolling updates Kubernetes, et la configuration par variables d'environnement.
Considérations de production
En production, plusieurs points méritent une attention particulière. La gestion mémoire en mode worker nécessite un monitoring des workers avec redémarrage périodique pour prévenir les fuites. Le debugging avec Xdebug reste supporté nativement, mais le comportement diffère en mode worker puisque le contexte persiste. Les extensions PHP compilées en C restent compatibles, FrankenPHP exécutant la même Zend Engine que PHP-FPM. Enfin, la configuration de Caddy offre une granularité suffisante pour les besoins de production (rate limiting, WAF, observabilité via les access logs structurés).
Synthèse
FrankenPHP représente une évolution architecturale significative pour l'écosystème PHP. En fusionnant le serveur web et l'interpréteur dans un processus unique, il élimine les couches intermédiaires qui pesaient sur les performances et la complexité opérationnelle. Le mode worker rompt avec le modèle share nothing quand les performances l'exigent, tout en préservant la possibilité de fonctionner en mode classique pour les cas où la robustesse de l'isolation prime. Pour les équipes qui déploient des applications PHP à fort trafic ou qui cherchent à simplifier leur infrastructure conteneurisée, FrankenPHP mérite une évaluation sérieuse.
Pour aller plus loin
- Pourquoi choisir Symfony pour vos projets — les atouts du framework pour vos applications web
- Tout savoir sur la mise en cache — optimiser les performances de vos applications PHP
- Retour sur le Forum PHP 2024 — les tendances et nouveautés de l'écosystème PHP
- Site officiel de FrankenPHP — documentation, installation et guides de démarrage
- Dépôt GitHub de FrankenPHP — code source et contributions
- Site officiel de Caddy — le serveur web sur lequel repose FrankenPHP
- PHP 9.0 : ce que vous devez savoir — les évolutions à venir du langage PHP