O PHP-FPM (FastCGI Process Manager) é o programa que de fato executa o seu código PHP num servidor web moderno. O nginx (ou o Apache) não entende PHP: quando chega uma requisição para index.php, ele repassa o pedido ao PHP-FPM, que mantém processos PHP já carregados — os workers — prontos para responder na hora. Se você já rodou sudo systemctl restart php8.3-fpm sem saber direito o que estava reiniciando, este artigo abre a caixa-preta: o protocolo que liga os dois lados, a arquitetura de master e workers, e os pools que organizam tudo.
Por que o servidor web não roda PHP sozinho?
O nginx é excelente em uma coisa: receber conexões e servir arquivos estáticos (HTML, CSS, imagens) com pouquíssima memória. Ele não tem interpretador PHP embutido — e isso é proposital. Historicamente, existiram três jeitos de juntar servidor web e PHP:
- Embutir o PHP no servidor (o
mod_phpdo Apache): o interpretador vive dentro de cada processo do Apache. Funciona, mas todo processo carrega o PHP inteiro na memória — até para servir uma imagem — e todos os sites da máquina dividem a mesma configuração e o mesmo usuário do sistema. - CGI clássico: o servidor abre um processo PHP novo a cada requisição e o mata no final. Isolamento perfeito, custo absurdo: carregar o interpretador, as extensões e compilar o script para tudo morrer um segundo depois.
- FastCGI com um gerenciador de processos — o modelo atual: o PHP vira um serviço separado e permanente (o PHP-FPM), e o servidor web só encaminha requisições para ele.
O terceiro modelo pega o melhor dos dois mundos: o isolamento do CGI (PHP e nginx são processos distintos, com usuários e limites próprios) sem pagar o preço de nascer e morrer a cada clique.
FastCGI: o idioma entre o nginx e o PHP
O FastCGI é um protocolo binário — uma evolução direta do CGI. Em vez de criar um processo e passar dados por variáveis de ambiente, o nginx abre uma conexão com o FPM (por Unix socket ou TCP) e envia a requisição empacotada em registros: os params — as mesmas variáveis do CGI, como REQUEST_METHOD, QUERY_STRING e a mais importante, SCRIPT_FILENAME, que diz qual arquivo executar — seguidos do corpo da requisição (um POST, por exemplo).
É exatamente isso que aquelas linhas misteriosas do nginx fazem:
location ~ \.php$ {
include snippets/fastcgi-php.conf; # monta os params (SCRIPT_FILENAME etc.)
fastcgi_pass unix:/run/php/php8.3-fpm.sock; # envia ao FPM
}
O ciclo completo de uma requisição fica assim:
- O pedido — o usuário acessa
seusite.com/index.php. - A delegação — o nginx traduz a requisição HTTP para registros FastCGI e envia pela conexão.
- A fila — o pacote chega ao FPM; um worker ocioso o pega.
- A execução — o worker roda o script e devolve a resposta (HTML, JSON…) ao nginx, que a repassa ao navegador.
- A reciclagem — o worker não morre: descarta tudo que era da requisição (variáveis, superglobais) e volta para a fila, com o interpretador e as extensões ainda carregados.
A conexão entre nginx e FPM também é reaproveitada entre requisições — mas cada worker atende uma requisição por vez, do início ao fim.
Dentro do FPM: o master e os workers
Quando o serviço sobe, nasce um processo master. Ele lê a configuração, abre os sockets de escuta e cria os workers (processos-filho). O master nunca executa PHP: o papel dele é gerenciar — monitorar os filhos, substituir os que travam ou morrem, crescer e encolher o time conforme a demanda. Por isso um fatal error ou um worker morto por falta de memória derruba aquela requisição, não o site inteiro: o master repõe o operário e a vida segue.
Essa separação também dá superpoderes operacionais:
- Reload gracioso:
systemctl reload php8.3-fpmfaz o master criar workers novos com a configuração nova enquanto os antigos terminam as requisições em andamento — deploy de configuração sem derrubar ninguém. - OPcache compartilhado: o cache de bytecode vive numa área de memória compartilhada entre os workers. O primeiro acesso compila o script; todos os workers seguintes pulam a compilação e vão direto para a execução. É a dupla FPM + OPcache que faz um framework carregado em milissegundos.
- Reciclagem programada: a diretiva
pm.max_requestsmanda o master reciclar cada worker depois de N requisições — uma cerca de contenção para vazamentos de memória de extensões ou do próprio código.
O 502 Bad Gateway mora nessa conexão
nginx e PHP-FPM são dois programas separados que precisam combinar um ponto de encontro: o listen do pool (lado FPM) e o fastcgi_pass (lado nginx) devem apontar para o mesmo endereço. Quando não apontam — ou quando o FPM está parado — o nginx devolve o famoso 502 Bad Gateway.
location ~ \.php$ {
include snippets/fastcgi-php.conf;
# o pool ouve em /run/php/php8.3-fpm.sock,
# mas o nginx aponta para outro caminho: 502
fastcgi_pass unix:/run/php/php-fpm.sock;
}# no pool: listen = /run/php/php8.3-fpm.sock
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
}E qual endereço usar? Depende de onde cada lado mora:
- Unix socket (
listen = /run/php/php8.3-fpm.sock) — nginx e FPM na mesma máquina. A comunicação nem passa pela pilha de rede; é o caminho mais curto. - TCP (
listen = 127.0.0.1:9000ou0.0.0.0:9000) — FPM em outra máquina ou outro container. É o padrão em Docker: o container do PHP escuta na porta 9000 e o nginx apontafastcgi_pass app:9000. Ganha-se a liberdade de escalar o PHP separado do servidor web.
"Editei o php.ini e nada mudou"
Cada forma de rodar PHP (cada SAPI) carrega o seu próprio php.ini. No Ubuntu/Debian, por exemplo:
/etc/php/8.3/cli/php.ini # usado pelo terminal (php, composer…)
/etc/php/8.3/fpm/php.ini # usado pelo site, via PHP-FPM
Pools: apartamentos isolados no mesmo prédio
Um pool é um conjunto de workers com identidade e regras próprias. Cada arquivo em /etc/php/8.3/fpm/pool.d/ define um pool (o padrão é o www.conf), e cada pool tem:
- Usuário do sistema próprio (
user/group): o site A roda comouser_a, o site B comouser_b. Se o site A for invadido, o atacante não tem permissão para ler os arquivos do B. - Socket próprio (
listen): cada pool é um ponto de encontro diferente para o nginx. - Limites e overrides próprios: dá para fixar configuração de PHP por pool —
php_admin_value[memory_limit] = 256Mnum pool,128Mno outro — sem tocar nophp.iniglobal. - Time de workers próprio: um script infinito no blog esgota só os workers do pool do blog; o checkout da loja, em outro pool, continua respondendo.
O pm: quantos workers ter?
Dentro de cada pool, quem decide o tamanho do time é o process manager:
pm = dynamic
pm.max_children = 20 ; teto absoluto de workers
pm.start_servers = 4
pm.min_spare_servers = 2
pm.max_spare_servers = 6
pm.max_requests = 500 ; recicla o worker após N requisições
| Modo | Como funciona | Quando usar |
|---|---|---|
static |
Número fixo de workers, sempre de pé | Servidor dedicado ao PHP, com RAM conhecida |
dynamic |
Mantém um mínimo e cresce até o teto | O padrão sensato para a maioria dos sites |
ondemand |
Só cria worker quando chega pedido (e mata os ociosos após pm.process_idle_timeout) |
Muitos sites pequenos com pouco tráfego |
Para dimensionar, volte à regra de ouro: um worker atende uma requisição por vez e ocupa memória de verdade (dezenas de MB com um framework carregado). Uma conta honesta de pm.max_children é RAM disponível para o PHP ÷ memória média de um worker.
E para enxergar o time trabalhando, ative a página de status do pool (pm.status_path = /status, protegida no nginx): ela mostra workers ativos e ociosos e a listen queue — requisições esperando worker livre. Fila crescendo é o sinal clássico de time pequeno demais. O FPM ainda tem o slowlog (request_slowlog_timeout + slowlog), que registra o rastro de qualquer requisição lenta demais — ouro puro para achar o gargalo.
Continue na árvore
O capítulo do manual sobre o FPM explica a instalação e o papel de cada peça.
Todas as diretivas de pool (listen, pm.*, limites) explicadas uma a uma.