A loja começa a devolver 502 no horário de pico. Alguém abre o www.conf, dobra o pm.max_children, reinicia o php-fpm e o erro some por vinte minutos. Depois volta pior, agora com o servidor em swap e o MySQL lento junto.
Esse ciclo tem duas causas: ninguém olhou qual erro era, e ninguém mediu quanta memória um worker do Magento realmente gasta. Dá para resolver os dois em poucos minutos.
502 e 504 são problemas diferentes
Antes de mexer em qualquer arquivo, leia o log de erro do nginx. Ele é bem explícito sobre o que aconteceu.
tail -n 200 /var/log/nginx/error.log | grep upstreamSe a linha fala em connect() to unix:/run/php/php8.3-fpm.sock failed (11: Resource temporarily unavailable), o nginx nem conseguiu entregar a requisição: todos os workers estavam ocupados e a fila de espera encheu. Isso é 502 por falta de worker, e aí sim o pm.max_children entra na conversa.
Se a linha fala em recv() failed (104: Connection reset by peer) while reading response header from upstream, o worker existia e morreu no meio — normalmente estouro de memory_limit ou segfault. Aumentar o pool não conserta nada aqui; o remédio está no var/log/ do Magento e no log do PHP.
E se a linha fala em upstream timed out (110: Connection timed out), é 504: a requisição passou do fastcgi_read_timeout. Worker sobrando não resolve uma página que demora demais.
Do lado do php-fpm, existe um aviso que fecha o diagnóstico:
grep -c "reached pm.max_children" /var/log/php8.3-fpm.logA mensagem é server reached pm.max_children setting, consider raising it. Se ela aparece dez vezes por dia no pico, o pool está apertado. Se não aparece nenhuma vez, o seu 502 é outra coisa e mexer no pool só vai gastar RAM à toa.
Quantos workers cabem no seu servidor
O pm.max_children é o teto de requisições PHP simultâneas. Cada uma delas é um processo com a memória dele. A conta é boba: memória disponível para PHP dividida pela memória média de um worker. O que quase ninguém faz é medir a segunda parte.
ps --no-headers -o rss -C php-fpm8.3 \
| awk '{s+=$1; n++} END {print int(s/n/1024)" MB por worker em "n" processos"}'Rode isso com a loja em uso, não às três da manhã. Em Magento o número costuma ficar bem acima do que a intuição diz, porque cada worker carrega o framework inteiro.
Do lado da memória disponível, tire do total o que MySQL, OpenSearch e Valkey já reservaram — e deixe folga para o sistema. Num servidor de 8 GB com 3 GB comprometidos, sobram uns 4,5 GB para o PHP com margem. Se o worker mediu 150 MB, o teto é 30, não 120.
Com o número na mão, o pool fica assim, em /etc/php/8.3/fpm/pool.d/www.conf:
pm = dynamic
pm.max_children = 30
pm.start_servers = 8
pm.min_spare_servers = 6
pm.max_spare_servers = 12
pm.max_requests = 500O pm.max_requests merece atenção: o padrão é 0, que significa worker imortal. Qualquer vazamento de memória em módulo de terceiro se acumula até o fim dos tempos. Reciclar o processo a cada algumas centenas de requisições custa quase nada e evita aquele crescimento lento que ninguém explica.
Depois de mudar, valide antes de reiniciar:
php-fpm8.3 -t
systemctl reload php8.3-fpm Os timeouts precisam conversar entre si
O nginx.conf.sample que vem no repositório do Magento define fastcgi_read_timeout 600s e fastcgi_connect_timeout 600s, além de fastcgi_buffers 16 16k e fastcgi_buffer_size 32k. Dez minutos é generoso de propósito: o mesmo arquivo é usado por rotas de admin que fazem trabalho pesado.
O problema é quando o lado do PHP não tem limite nenhum. O request_terminate_timeout nasce em 0: sem ele, uma requisição travada segura o worker para sempre e vai comendo o seu pool até chegar no 502. Eu deixo o php-fpm cortar antes do nginx desistir:
request_terminate_timeout = 300
request_slowlog_timeout = 10s
slowlog = /var/log/php8.3-fpm.slow.logCom essa ordem, quem mata a requisição é o php-fpm, e o slowlog grava o backtrace de qual função estava rodando. Se o nginx corta primeiro, você fica com um 504 e nenhuma pista.
Vale lembrar que cron, consumers e bin/magento rodam pelo PHP de linha de comando e não ocupam o pool do FPM. Se o seu 502 aparece exatamente quando o catalog:images:resize está rodando, o problema é disputa de CPU e memória no servidor, não tamanho de pool.
Para enxergar a fila em tempo real, ligue o status do pool — ele nasce desligado:
pm.status_path = /statusE exponha só para o próprio servidor, num bloco à parte do nginx com allow 127.0.0.1; e deny all;. O campo que interessa é o listen queue: se ele vive acima de zero, tem requisição esperando worker. Se vive em zero e você continua vendo 502, pare de mexer no pool — o gargalo está em outro lugar, e o guia de performance cobre os suspeitos seguintes.
Perguntas rápidas
Uso pm = dynamic, static ou ondemand?
Em servidor dedicado à loja, static com o teto que a memória permite dá o tempo de resposta mais estável, porque não paga o custo de subir processo no pico. Dynamic é a escolha segura quando o servidor divide memória com outras coisas, e ondemand só faz sentido em ambiente de baixo tráfego ou de desenvolvimento.
Posso simplesmente aumentar o memory_limit do PHP para parar os 502?
Cuidado: memory_limit maior por worker significa menos workers cabendo na mesma RAM. Se você subir o limite sem baixar o max_children, o servidor entra em swap no pico e o site fica lento em vez de dar erro, que é mais difícil de diagnosticar.
Faz sentido ter um pool separado para o admin?
Faz, e é uma das mudanças que mais rendem em loja movimentada. O admin faz requisições longas de relatório e grid que seguram worker por muito tempo; com pool próprio, essa carga deixa de competir com o cliente que está tentando fechar o pedido.
Pra conferir na fonte
- PHP-FPM configuration — PHP Manual
- Module ngx_http_fastcgi_module — nginx
- nginx.conf.sample do Magento 2 — GitHub