Situação atual: PHP2Shell. Duas vulnerabilidades críticas do PHP (CVSS 9,2) permitem executar código malicioso sem autenticação. Patches disponíveis desde 21/10/2026.O que fazer agora →

Definir limites de memória com systemd e cgroups

O php.net recomenda, como medida transitória do PHP2Shell, memory_limit=-1 e um limite de memória fora do PHP. Como definir esse limite com systemd e cgroups sem que um único worker arraste todo o serviço.

Atualizado em 6 min de leituraHardening e configuração

Em resumo: Para a medida transitória do PHP2Shell memory_limit=-1, o PHP-FPM precisa de um limite de memória fora do PHP. No systemd, defina-o com MemoryHigh= como travão e MemoryMax= como limite máximo rígido, num ficheiro complementar para o serviço FPM. ulimit, LimitAS= ou LimitDATA= não são adequados para isso, segundo o php.net. Verifique também o OOMPolicy=, para que um único worker não termine todo o serviço.

Uma recomendação que, à primeira, surpreende

Entre as medidas transitórias que o php.net publicou a 21 de outubro de 2026 sobre o PHP2Shell, há uma que fez muitos administradores hesitar: memory_limit=-1, ou seja, nenhum limite de memória no próprio PHP. Durante anos, o contrário foi considerado boa prática.

Há uma relação óbvia com a descrição da vulnerabilidade. Na CVE-2026-109618, um estado de falta de memória pode interromper o PHP enquanto os dados do pedido estão apenas parcialmente inicializados. A medida transitória transfere o limite de memória do PHP para o sistema operativo. O php.net associa-a expressamente a duas condições: o valor tem de estar bloqueado contra sobrescrita e o limite tem de vir de um contentor ou de uma cgroup, não de RLIMIT_AS ou RLIMIT_DATA.

Quem aplica apenas a primeira metade fica com um servidor sem qualquer limite de memória. Este artigo mostra a segunda metade. Mesmo em conjunto, a medida continua a ser transitória: a vulnerabilidade só fica corrigida com o PHP 8.5.12, 8.4.27, 8.3.36, 8.2.35 ou um backport confirmado.

cgroups explicadas em duas frases

Os Control Groups, abreviadamente cgroups, são uma funcionalidade do kernel Linux que agrupa processos e limita os seus recursos. O systemd cria automaticamente uma cgroup própria para cada serviço, e todos os processos do serviço, no caso do serviço FPM o master e todos os workers, fazem parte dela.

As opções de memória do systemd definem valores do controlador de memória cgroup v2. Se o seu sistema usa cgroup v2 mostra-o um comando breve:

# "cgroup2fs" significa: cgroup v2 está ativa
stat -fc %T /sys/fs/cgroup/

As versões atuais das distribuições mais comuns usam cgroup v2 por defeito. Se o comando mostrar outra coisa, esclareça isso antes da alteração, porque a documentação do systemd descreve as antigas opções v1 como obsoletas.

Definir o systemd MemoryMax para o PHP-FPM

O limite vai para um ficheiro complementar, um chamado drop-in. Assim, o ficheiro de unidade do pacote fica inalterado e uma atualização não sobrescreve a sua definição. Os nomes dos serviços variam consoante a distribuição; o exemplo usa Debian e Ubuntu com PHP 8.3.

# Abre um editor para o drop-in: sudo systemctl edit php8.3-fpm
# Resultado: /etc/systemd/system/php8.3-fpm.service.d/override.conf
[Service]
MemoryAccounting=yes
MemoryHigh=1536M
MemoryMax=2G
MemorySwapMax=0
OOMPolicy=continue

As opções em resumo, segundo a documentação do systemd:

Opção Efeito Nota para o PHP-FPM
MemoryHigh= limite de estrangulamento: os processos são fortemente travados, a memória é recuperada o parâmetro de controlo propriamente dito, um pouco abaixo de MemoryMax
MemoryMax= limite absoluto: se não puder ser mantido, o OOM-Killer atua no serviço última linha de defesa
MemorySwapMax= limite para a troca 0 impede que o serviço recorra, em vez disso, ao swap
OOMPolicy= comportamento após um evento OOM: continue, stop ou kill continue deixa os restantes workers continuar a correr
LimitAS=, LimitDATA= rlimits por processo segundo o php.net, inadequados para esta medida

O OOMPolicy= merece atenção especial. Com stop ou kill, o systemd termina, após um evento OOM, todo o serviço, e todos os sites desse serviço FPM ficam indisponíveis. Que valor se aplica sem indicação própria é definido pelo padrão do sistema DefaultOOMPolicy=. Verifique por isso o valor efetivo, em vez de o presumir.

Depois, reinicie o serviço e bloqueie o memory_limit no pool:

sudo systemctl restart php8.3-fpm
systemctl show php8.3-fpm -p MemoryHigh -p MemoryMax -p MemorySwapMax -p OOMPolicy -p MemoryCurrent
; Ficheiro do pool, p. ex. /etc/php/8.3/fpm/pool.d/seite1.conf
php_admin_value[memory_limit] = -1

Como funciona o bloqueio é descrito em php_admin_value: bloquear definições contra a sobrescrita.

Encontrar o valor certo

Um limite demasiado curto gera interrupções na operação normal, um demasiado generoso protege pouco. O ponto de partida é o consumo real.

  1. Observar durante alguns dias a necessidade atual de memória de todos os processos FPM, também nos picos de carga.
  2. Determinar a necessidade típica de um worker e multiplicá-la pelo pm.max_children de todos os pools do serviço.
  3. Prever um acréscimo para o processo master, a OPcache e os picos.
  4. Definir MemoryHigh= ligeiramente acima do valor de pico medido e MemoryMax= com alguma margem acima disso.
  5. Reajustar os valores após uma semana, com base nas medições e nos eventos OOM.
# Memória de todos os processos PHP-FPM em MB (Resident Set Size)
ps -C php-fpm8.3 -o rss= | awk '{s+=$1} END {printf "%.0f MB\n", s/1024}'
# Vista contínua de todas as cgroups ordenada por memória
systemd-cgtop -m

Deve anotar o valor do memory_limit antes da alteração. Após a atualização, pode voltar a um limite fixo no PHP; como o escolher com critério está em Definir corretamente o memory_limit.

Monitorizar: quando o limite atua

Um limite de memória cujo disparo ninguém nota é uma fonte silenciosa de erros. O kernel conta os eventos OOM por cgroup, e o systemd e o registo do kernel registam-nos.

# Contador OOM da cgroup do serviço (caminho com cgroup v2)
cat /sys/fs/cgroup/system.slice/php8.3-fpm.service/memory.events
# Mensagens do OOM-Killer no registo do kernel
journalctl -k --since "-1 day" | grep -i "out of memory"

Se o contador oom_kill subir, o kernel está a terminar workers. Isso pode indicar um limite demasiado curto, uma aplicação defeituosa ou pedidos invulgares. Na situação atual, vale a pena comparar esses eventos com os registos de acesso à mesma hora.

Importante

O limite de memória é uma medida transitória e não substitui a atualização do PHP. Planeie a atualização em paralelo e reinicie depois todos os processos PHP. Encontra todas as medidas transitórias em Medidas imediatas, quando ainda não pode aplicar o patch.

O que acontece no interior do PHP em caso de falta de memória é explicado em memory_limit e Out-of-Memory. No Apache com mod_php vale a mesma técnica para o serviço Apache, embora o PHP partilhe então o limite com o próprio servidor web.

Conclusão: não esquecer a segunda metade

O memory_limit=-1 só é uma medida transitória defensável em conjunto com um limite exterior. O systemd torna esse limite possível com poucas linhas: MemoryHigh como travão, MemoryMax como limite máximo rígido, MemorySwapMax contra o recurso ao swap e uma OOMPolicy escolhida de forma consciente. Meça antes, observe depois e tenha presente que todos os pools de um serviço partilham o limite.

Acima de tudo: planeie o fim da medida. Depois da atualização para uma versão corrigida do PHP, volte a um memory_limit fixo. O limite cgroup pode manter-se como proteção adicional contra processos descontrolados.

Como ajuda concretamente a PHP SOS?

  • Medir: registamos a necessidade de memória dos seus serviços PHP e deduzimos daí limites adequados.
  • Aplicar: definimos drop-ins com MemoryHigh, MemoryMax e OOMPolicy adequada, bloqueamos o memory_limit no pool e reiniciamos os serviços de forma controlada.
  • Observar: integramos os eventos OOM na monitorização e reajustamos os valores.
  • Conduzir ao patch: através do serviço de patches PHP, aplicamos a atualização e retiramos depois, de forma ordenada, os valores transitórios.

Perguntas frequentes

Por que deve o memory_limit estar em -1? Isso não é perigoso?

Sozinho, seria. O php.net recomenda o memory_limit=-1 como medida transitória expressamente juntamente com um limite de memória através de contentor ou cgroup. A limitação passa assim do PHP para o sistema operativo. Sem este limite exterior, não deve definir o valor.

Posso usar antes o ulimit ou LimitAS=?

Não. O php.net desaconselha expressamente RLIMIT_AS e RLIMIT_DATA, e são precisamente estes limites que o ulimit -v, LimitAS= e LimitDATA= definem. A documentação do systemd alerta ainda que LimitAS= limita o espaço de endereçamento, não o consumo de memória.

O que acontece quando o PHP-FPM atinge o limite?

Com MemoryHigh, o kernel trava os processos e recupera memória. Com MemoryMax, o OOM-Killer passa a atuar dentro do serviço e termina um processo, em geral um worker grande. Se depois todo o serviço pára é regulado por OOMPolicy=.

O limite aplica-se a cada pool individualmente?

Não. Todos os pools de um serviço FPM partilham uma cgroup e, portanto, um limite comum. Limites separados por site exigem serviços FPM ou contentores separados.

Como defino o limite em Docker ou Kubernetes?

Através da limitação de memória do contentor, por exemplo a opção --memory no docker run ou resources.limits.memory no Kubernetes. Ambos usam igualmente cgroups. O princípio mantém-se: limite fora do PHP, memory_limit bloqueado no PHP.

Fontes

Contacto imediato

Cada hora conta. Escreva-nos agora.

Descreva brevemente o sistema, a versão do PHP e o que observou. Entramos em contacto consigo, esclarecemos a situação e trabalhamos remotamente até o incidente estar concluído e documentado.

Ajuda imediata