Provar um upgrade de PHP antes de mudar, com um rollback pronto
Mudar uma aplicação para um novo branch de PHP em segurança: instalar lado a lado, fazer lint e scan ao código, rastrear o que o tráfego realmente carrega, e depois mudar um socket.

A maioria dos upgrades de PHP falha da mesma forma: a nova versão é instalada por cima da antiga, a página inicial carrega, toda a gente relaxa, e o primeiro administrador a abrir um ecrã raramente usado na manhã seguinte recebe uma página branca. Aqui fica o método que usamos para encontrar esses problemas na bancada, antes do cutover, com um caminho de regresso que demora segundos.
Porque é que os upgrades continuam a partir coisas
Cada branch de PHP remove funções, aperta o tratamento de tipos e transforma avisos antigos em fatal errors. Uma aplicação que cresceu ao longo de anos carrega caminhos de código que ninguém exercitou há muito tempo: uma exportação, um módulo de pagamentos legado, um relatório de administração. Um smoke test das páginas públicas nunca lá chegará.
As aplicações comerciais acrescentam uma segunda camada. Os ficheiros protegidos por um encoder só correm se o loader para o novo branch de PHP suportar o formato com que foram codificados, e isso é uma propriedade de cada ficheiro, não da página de marketing da aplicação.
Primeiro passo: instale lado a lado, nunca por cima
Instale o PHP de destino ao lado do atual. Em RHEL e nas suas reconstruções, as software collections do Remi tornam isto simples: cada branch vive no seu próprio prefixo, com o seu próprio serviço FPM e o seu próprio socket. Crie um pool para o site na nova versão, idêntico ao pool antigo exceto no binário e no caminho do socket.
Nada mudou ainda para os visitantes. Ambas as versões estão a correr, e o web server continua a apontar para o socket antigo.
Segundo passo: faça lint a todos os ficheiros com o novo binário
O teste mais barato é pedir ao novo PHP que faça parse de todos os ficheiros:
NEW_PHP=/path/to/new/php
find /var/www/app -name '*.php' -print0 \
| xargs -0 -n1 -P8 "$NEW_PHP" -l 2>&1 \
| grep -v '^No syntax errors'
Isto apanha sintaxe que o novo branch já não aceita. Não apanha funções removidas nem comportamento alterado, porque esses só falham em tempo de execução. Para isso, corra um scan estático de compatibilidade, como o PHP_CodeSniffer com o standard PHPCompatibility, configurado para o seu branch de destino:
phpcs -p --standard=PHPCompatibility --runtime-set testVersion <target> \
--extensions=php /var/www/app
Conte com ruído de bibliotecas vendored e de código morto. A lista é um mapa, não um veredicto.
Terceiro passo: descubra que ficheiros o tráfego carrega realmente
Uma aplicação pode conter dezenas de milhares de ficheiros PHP, dos quais só uma fração alguma vez corre. Quer que o esforço de compatibilidade seja gasto nessa fração, e quer ter a certeza de que inclui os caminhos de utilizadores autenticados e de administração.
A OPcache já sabe. Depois de um dia de trabalho normal na versão atual, com pessoas a autenticar-se e administradores a fazer o seu trabalho, pergunte-lhe que scripts compilou:
<?php
// protect this file: admin network only, and delete it afterwards
$s = opcache_get_status(true);
foreach (array_keys($s['scripts']) as $f) { echo $f, "\n"; }
Guarde a lista, compare-a com os resultados do lint e do scan, e os problemas que vão realmente ser atingidos sobem ao topo. Os ficheiros que aparecem no scan mas nunca no rastreio são candidatos a remoção, não a correção.
Quarto passo: leia os cabeçalhos dos ficheiros codificados
Para software comercial codificado, verifique cada ficheiro em vez de confiar num banner de compatibilidade. Os ficheiros codificados trazem um cabeçalho em texto simples que nomeia o encoder e o formato:
grep -rl --include='*.php' 'ionCube Loader' /var/www/app \
| while read -r f; do head -c 400 "$f" | strings | head -3; done | sort | uniq -c
Agrupe os ficheiros por formato de encoder, e depois confirme que o loader para o seu branch de PHP de destino executa esse formato. Um módulo codificado há anos para um branch antigo é exatamente o ficheiro que se vai recusar a carregar no novo, e é melhor sabê-lo por um cabeçalho do que por um cliente.
Quinto passo: ensaie no novo pool
Aponte um hostname de teste, ou o seu próprio IP através de uma regra de header, para o novo socket enquanto a produção fica no antigo. Percorra os caminhos do rastreio da OPcache, com especial atenção aos que só os utilizadores autenticados e os administradores alcançam, e observe o error log do novo pool enquanto o faz:
tail -F /var/log/php-fpm/app-new-error.log | grep -E 'Fatal|Uncaught|Deprecated'
Os avisos de deprecation não são falhas hoje, mas são a lista para o próximo upgrade. Anote-os.
Sexto passo: mude uma linha, mantenha o pool antigo a correr
O cutover é uma alteração no web server: o socket FastCGI.
# fastcgi_pass unix:/run/php-fpm/app-old.sock;
fastcgi_pass unix:/run/php-fpm/app-new.sock;
nginx -t && systemctl reload nginx
O rollback é a mesma edição ao contrário, aplicada em segundos, sem alterações de pacotes e sem reinícios de nada além da configuração do web server. Acorde o momento com o dono do site, observe as taxas de erro e os tempos de resposta durante a primeira hora, e mantenha o pool antigo a correr durante um período de burn-in suficientemente longo para cobrir tarefas semanais e mensais. Só então o retire.
Numa stack de vários nós, faça-o um nó web de cada vez atrás do load balancer, para que mesmo uma surpresa desagradável chegue a uma fração do tráfego durante poucos minutos.
Sétimo passo: mantenha cron e CLI a par
O web server não é a única coisa a correr PHP. Tarefas agendadas, queue workers e ferramentas de linha de comandos chamam frequentemente um caminho de binário fixo no código. Encontre-os:
grep -rE 'php[0-9]*( |$)|/bin/php' /etc/cron* /var/spool/cron /etc/systemd/system 2>/dev/null
Mova-os para o novo binário na mesma janela de alteração, ou deliberadamente depois, mas nunca por acidente.
A sua checklist
- Novo PHP instalado ao lado do antigo, com o seu próprio pool e socket.
- Todos os ficheiros com lint pelo novo binário, e um scan de compatibilidade para o branch de destino.
- Um rastreio da OPcache do que o tráfego real carrega, incluindo os caminhos de utilizadores autenticados e de administração.
- Ficheiros codificados agrupados por formato e confrontados com o novo loader.
- Um ensaio no novo pool com o seu error log aberto.
- Um cutover de uma linha, o pool antigo mantido até ao fim do burn-in, e o cron movido de propósito.
Se tem um upgrade que tem vindo a adiar, teremos todo o gosto em planeá-lo consigo.
Related
- /what-we-do/data-web/php-upgrades-security-patching/
- /what-we-do/data-web/performance-audits/
- /what-we-do/data-web/high-availability-web-tiers/
Publicado em 15 de setembro de 2026.
Todas as notas de campo