← Blog

PHP na web: Nginx, FPM e o ciclo real de uma request

“PHP é lento” costuma ser diagnóstico de quem não olhou o caminho da request. A linguagem interpreta um script e devolve HTML ou JSON. O que decide se isso leva 40 ms ou 4 s é, com frequência, o processo que executa, o tamanho do pool, o que o script espera e o que o Nginx faz antes de chegar no PHP.

Entender o ciclo não é saudosismo de sysadmin. É o chão de qualquer conversa séria sobre disponibilidade em PHP. Sem esse mapa, a gente otimiza o framework e ignora a fila de processos.

O que o navegador realmente aciona

O cliente fala HTTP com o Nginx (ou outro servidor web). Arquivo estático — CSS, imagem, PDF — o Nginx serve sozinho. Script PHP não. O Nginx encaminha para o PHP-FPM por FastCGI, em geral via socket Unix ou TCP local.

O FPM é um gerenciador de processos. Há um mestre que não executa sua aplicação. Ele mantém um ou mais pools: conjuntos de workers. Cada worker é um processo PHP pronto (ou quase pronto) para receber uma request, rodar o script e devolver a resposta. Uma request, na configuração clássica, ocupa um worker até o fim.

Isso importa. Se você tem 10 workers e 40 requests simultâneas que demoram 2 s cada, 30 esperam. O usuário vê timeout. O monitor vê CPU baixa. A conclusão errada é “PHP não escala”. A conclusão certa é “o pool está saturado de trabalho lento”.

flowchart TD
  A[Cliente HTTP] --> B[Nginx]
  B -->|estático| C[Arquivo]
  B -->|PHP| D[PHP-FPM mestre]
  D --> E[Worker do pool]
  E --> F[Script + app]
  F --> G[Resposta]
  G --> B
  E --> H[Worker livre de novo]
  classDef ouro fill:#FBF5E4,stroke:#C9A227,color:#0B1F18,stroke-width:1.4px
  classDef verde fill:#F3FAF7,stroke:#1E7A57,color:#0B1F18,stroke-width:1.4px
  classDef fim fill:#14513B,stroke:#0F3328,color:#FBFAF6,stroke-width:1.4px
  class A,B ouro
  class D,E,F verde
  class C,G,H fim

Três jeitos de criar processos

O pool tem política de criação. Static já sobe pm.max_children workers e os mantém. Não há custo de fork no pico. Há custo fixo de memória. Se cada processo come 80 MB e você põe 40 filhos, reserve uns 3 GB só para o FPM — e ainda deixe folga para o sistema e o banco, se estiver na mesma caixa.

On-demand nasce processo quando chega request e morre depois de ocioso. Economiza memória em site quieto. No pico, paga o preço de criar processo exatamente quando você menos quer.

Dynamic tenta o meio: um mínimo vivo, um máximo, e um alvo de ociosos. É o padrão de muita instalação. Também é o padrão que esconde surpresa: o máximo é pequeno, o tráfego sobe, o Nginx espera o FPM, o timeout estoura.

Não existe número universal. O cálculo começa pela memória: quanto o processo realmente usa depois de aquecido, não no php -v. Depois pela concorrência que você precisa. Depois pelo tempo de cada request. Pool dimensionado no chute é a origem da lenda da lentidão.

Política Serve quando Cuidado
Static Carga previsível, memória conhecida Superdimensionar come RAM à toa
Dynamic Tráfego oscila ao longo do dia max_children baixo vira fila invisível
On-demand Quase idle, picos raros Fork no pico aumenta latência

O que o FPM ainda decide por você

Além de quantos filhos, o FPM decide o que fazer com processo doente. pm.max_requests recicla o worker depois de N requests — útil quando uma extensão vaza memória. request_terminate_timeout mata script que não termina. No mestre, emergency_restart_threshold e emergency_restart_interval reiniciam o FPM se muitos filhos morrem num intervalo: sinal de que o problema não é um request, é o pool inteiro.

Há também fpm_get_status() e a página de status do FPM. Idle, active, listen queue: esses números explicam mais incidentes do que o flamegraph prematuro. Fila de listen crescendo com CPU baixa é pool pequeno ou request longa. CPU no teto com idle zero é outra conversa — aí sim, código ou falta de máquina.

Opcache entra neste ciclo. Sem ele, cada request recompila. Com ele, o worker reutiliza bytecode. “PHP é lento” em produção sem opcache é, muitas vezes, configuração, não linguagem. JIT ajuda em laços pesados; na request web típica, opcache e I/O bem resolvidos pesam mais.

Por que a request segura o worker

PHP-FPM clássico não é event loop. Enquanto o script espera SMTP, HTTP de terceiro ou query, o processo está ocupado. Ele não atende a próxima pessoa. Por isso e-mail síncrono e relatório na request derrubam concorrência mesmo com CPU ociosa.

A saída não é “trocar de linguagem”. É encurtar o que o worker precisa terminar: índice, cache, fila, timeout agressivo em cliente HTTP, menos trabalho no bootstrap. Runtimes persistentes (Swoole, FrankenPHP, Octane) mudam o modelo — processo longo, menos bootstrap — e trazem outro conjunto de cuidados: memória, estado vazando entre requests, extensão não thread-safe. Útil, não mágico. O mapa mental continua: quem segura a concorrência?

Quem está aprendendo a linguagem pelo PHP One encontra o ciclo request–resposta cedo, e é o lugar certo: framework nenhum apaga o FPM.

Um roteiro curto quando “está lento”

  1. O Nginx está servindo estático ou tudo passa no PHP?
  2. Quantos workers, quantos ativos, qual a listen queue?
  3. Quanto RAM cada filho usa de verdade?
  4. Qual rota segura o worker por mais tempo?
  5. Esse tempo é CPU ou espera?

Se a fila cresce e a CPU não, não comece por instância maior. Comece por request longa e por max_children. Se a CPU sobe com workers saturados e o código já está enxuto, aí o eixo vira escala — vertical na caixa ou mais instâncias atrás do balanceador.

Disponibilidade em PHP é, em grande parte, gestão de processo. A linguagem executa o script. O FPM decide quantos scripts ao mesmo tempo. O Nginx decide quem espera. Quando os três estão visíveis, a frase “PHP é lento” perde o emprego.

Leia também: Escalabilidade vertical e horizontal e Load balancer, Nginx e vários servidores.

#PHP#FPM#Nginx#Web

Thiago Nogueira

Autor de A Caixa Mágica e das séries Programming One e AI One na Editora Pensar Bem.

Capa de PHP One

PHP One

O PHP moderno do zero, antes de qualquer framework.