Se você roda PHP em produção com Nginx + PHP-FPM, sabe como funciona: cada request dispara um processo, carrega o framework inteiro, monta o container de dependências, executa a lógica, responde, e descarta tudo. Na próxima request, repete do zero. Esse modelo share-nothing serviu bem por duas décadas, mas cobra um preço em latência e consumo de recursos que ficou difícil de ignorar.

Em maio de 2025, a PHP Foundation oficializou o suporte ao FrankenPHP, transferindo o repositório para a organização php/ no GitHub. O projeto de Kévin Dunglas deixou de ser uma aposta individual para virar infraestrutura oficial do ecossistema. E os números explicam o motivo.

O que é FrankenPHP e por que a PHP Foundation o adotou

FrankenPHP é um app server PHP escrito em Go que embute o interpretador PHP diretamente dentro do Caddy, o web server que já traz HTTP/3, HTTPS automático e compressão Zstandard de fábrica. Em vez de depender do FastCGI para comunicar Nginx com PHP-FPM, o FrankenPHP elimina essa camada intermediária: o PHP roda dentro do mesmo processo que serve HTTP.

O projeto acumula mais de 8.000 stars no GitHub e mais de 100 contribuidores. A adoção pela PHP Foundation em maio de 2025 significou a transferência do código-fonte e parte da documentação para a organização oficial, com manutenção compartilhada entre Kévin Dunglas, Robert Landers e Alexander Stecher. Provedores como Laravel Cloud, Upsun e Clever Cloud já o hospedam nativamente.

O que me chama atenção nessa movimentação é o pragmatismo. A Foundation não adotou o FrankenPHP por ser novidade, mas porque frameworks como Laravel, Symfony e Yii já ofereciam integrações oficiais com worker mode antes mesmo da transferência. O ecossistema votou com código.

Arquitetura: Go, Caddy e o embed do interpretador PHP

A arquitetura do FrankenPHP pode ser resumida assim: o interpretador C do PHP é compilado como uma biblioteca e carregado dentro de um binário Go via CGO (a bridge entre Go e C). Esse binário Go é um módulo do Caddy.

O resultado é um único processo que:

  • Serve HTTP/1.1, HTTP/2 e HTTP/3
  • Gera e renova certificados TLS automaticamente via ACME
  • Comprime respostas com Zstandard, Brotli ou Gzip
  • Suporta 103 Early Hints (o FrankenPHP é o único SAPI PHP com esse recurso)
  • Executa código PHP sem FastCGI

A configuração mínima em produção cabe em três linhas de Caddyfile:

{
  frankenphp
}

localhost {
  root * /app/public
  php_server
}

Isso entrega HTTPS automático, HTTP/3 e compressão sem configuração adicional. Compara com o setup equivalente de Nginx + PHP-FPM + Certbot + módulos de compressão, e a diferença de complexidade operacional fica evidente.

Como o FrankenPHP também é uma biblioteca Go standalone (net/http compatible), ele pode ser embarcado em qualquer aplicação Go que precise executar PHP. Não é o caso de uso mais comum, mas abre possibilidades para quem mantém backends híbridos.

Worker mode: boot uma vez, sirva milhares

O modo clássico do FrankenPHP funciona como um drop-in replacement do PHP-FPM: cada request carrega e descarta o estado PHP normalmente. É útil para migração gradual, mas não é onde o ganho real mora.

O worker mode muda a equação. Em vez de descartar tudo entre requests, o FrankenPHP mantém a aplicação PHP em memória. O framework é inicializado uma vez (bootstrap do container, carregamento de configurações, resolução de rotas), e a partir daí cada request reutiliza esse estado quente.

A mecânica se parece com o que Node.js e Go fazem naturalmente: o processo sobe, carrega a aplicação, e fica escutando. A diferença é que o PHP não foi desenhado para isso. O modelo share-nothing era uma feature de segurança (nenhum request contamina o próximo). O worker mode precisa de cuidados extras para resetar estado entre requests: variáveis globais, sessões e configurações INI são limpos automaticamente pelo FrankenPHP, mas código que depende de singletons mutáveis ou cache em memória estática pode precisar de ajustes.

Para Laravel, a integração é via Octane:

composer require laravel/octane
php artisan octane:install --server=frankenphp
php artisan octane:start

Para Symfony, é via Runtime Component:

composer require runtime/frankenphp-symfony
APP_RUNTIME=Runtime\FrankenPhpSymfony\Runtime php public/index.php

Nos dois casos, o código da aplicação não precisa mudar. O framework cuida de adaptar o ciclo de vida.

Benchmarks: FrankenPHP vs PHP-FPM em números

Vou separar os números por modo de operação, porque a diferença entre classic e worker mode é grande.

Classic mode vs PHP-FPM

O Tideways publicou um benchmark em dezembro de 2025 comparando FrankenPHP classic mode contra Nginx + PHP-FPM. Os resultados: praticamente empate.

  • Hello World: PHP-FPM 18.479 req/s vs FrankenPHP 18.404 req/s
  • HTML response: PHP-FPM 7.023 req/s vs FrankenPHP 6.934 req/s
  • P99 latência (HTML): PHP-FPM 2.02ms vs FrankenPHP 2.06ms

A conclusão deles foi direta: o modo clássico não traz ganho de performance sobre PHP-FPM. As pequenas variações vinham de diferenças de compressão entre Nginx e Caddy, não do PHP em si.

Worker mode vs PHP-FPM

Aqui o cenário muda. Ivan Vulovic publicou uma série de benchmarks com Symfony em uma instância AWS, e os resultados em worker mode foram consistentes:

  • Throughput: FrankenPHP ~15.000 req/s vs PHP-FPM ~4.000 req/s (aproximadamente 4x)
  • Em carga alta (2.000 req/s simultâneos): latência média do PHP-FPM disparou para quase 1 segundo, enquanto FrankenPHP manteve ~5ms
  • Zero requests falhados no FrankenPHP sob carga que causava timeouts no PHP-FPM

A Sylius (plataforma de e-commerce baseada em Symfony) reportou que o worker mode reduziu tempos de resposta em 80% e permitiu servir o mesmo tráfego com 6x menos máquinas.

Esses números fazem sentido quando você entende o que está sendo eliminado. Em uma aplicação Symfony ou Laravel, o bootstrap do framework pode levar 20-50ms. Em worker mode, esse custo é pago uma vez. Se sua aplicação atende 1.000 requests por segundo, são 20-50 segundos de CPU que você deixa de gastar por segundo. A matemática é simples.

Na prática: Laravel Octane e Symfony Runtime

A integração com Laravel via Octane é a mais madura. O Octane já suportava Swoole e RoadRunner antes do FrankenPHP, então o modelo mental já é conhecido: você instala o driver, configura o Caddyfile se quiser customizar, e inicia o server.

Um Caddyfile de produção com Laravel Octane se parece com isso:

{
  frankenphp
  order php_server before file_server
}

localhost {
  root * /app/public

  php_server {
    worker {
      file /app/public/frankenphp-worker.php
      watch
    }
  }
}

A diretiva watch recarrega o worker automaticamente quando arquivos mudam, útil em desenvolvimento.

No Docker, a configuração se resume a:

docker run -v $PWD:/app -p 443:443 dunglas/frankenphp

O container já vem com PHP, Caddy e worker mode prontos. HTTPS automático funciona até em localhost (com certificados auto-assinados para desenvolvimento).

Para Symfony, a integração é nativa a partir do Runtime Component. O pacote runtime/frankenphp-symfony adapta o ciclo de vida do kernel Symfony para rodar em modo persistente. A partir do Symfony 7.4, o suporte é direto, sem pacotes adicionais.

Quando não trocar (e os cuidados com worker mode)

Worker mode não é uma bala de prata. Alguns cenários onde PHP-FPM continua sendo a escolha mais segura:

  • Aplicações que dependem de extensões PHP incompatíveis com o worker mode (ex: extensões que mantêm estado global não-resetável)
  • Código legado que usa variáveis globais de forma imprevisível
  • Ambientes onde o modelo share-nothing é um requisito de segurança (multi-tenant com isolamento rígido entre requests)

A versão 1.11.2 (fevereiro de 2026) corrigiu um vazamento de sessão entre requests em worker mode (GHSA-r3xh-3r3w-47gp). Esse tipo de bug demonstra que o modelo persistente introduz uma classe de problemas que não existe no PHP-FPM. Cada release do FrankenPHP adiciona mais proteções (reset automático de $_SESSION, $_REQUEST e configurações INI). Em produção, configurar max_requests para reiniciar workers periodicamente ajuda a prevenir memory leaks em aplicações com alto volume.

Outra limitação: o modo clássico do FrankenPHP não traz ganho sobre PHP-FPM. Se você não pode usar worker mode por restrições da aplicação, a migração não compensa.

A versão 1.11.3 (25 de fevereiro de 2026) adicionou suporte a PHP 8.5, e a 1.11.2 trouxe ganhos de performance do Go 1.26 (~30% em chamadas CGO, 10-40% no garbage collector). O projeto está em ritmo acelerado de releases.

Conclusão

O FrankenPHP resolve um problema específico e resolve bem: eliminar o overhead de bootstrap em aplicações PHP que rodam frameworks pesados. Se você mantém Laravel ou Symfony em produção com PHP-FPM e cada request gasta 30-50ms só para inicializar o framework, o worker mode pode cortar isso para perto de zero.

A adoção pela PHP Foundation em 2025 tirou o risco de "projeto de uma pessoa só". Com o código sob php/ no GitHub, manutenção compartilhada e hosting nativo em provedores como Laravel Cloud e Clever Cloud, a barreira de entrada para testar caiu bastante.

Para quem quer experimentar sem compromisso: o modo clássico funciona como drop-in replacement. Troque Nginx + PHP-FPM por FrankenPHP classic, valide que tudo funciona, e depois ative worker mode quando estiver confortável. Sem reescrita de código.

Referências pesquisadas nesta publicação