# Definir limites de memória com systemd e cgroups

> systemd MemoryMax para PHP-FPM: definir o limite de memória por cgroup, como o php.net recomenda para o PHP2Shell, com MemoryHigh, OOMPolicy e comandos de teste.

Fonte: https://phpsos.de/pt/blog/speichergrenzen-systemd-cgroups/  
Atualizado em: 25/10/2026  
Prestador: PHP SOS, Assistência de emergência PHP e segurança de servidores. Ajuda imediata por WhatsApp: https://wa.me/491736494914?text=Ol%C3%A1%2C%20PHP%20SOS%2C%20precisamos%20de%20ajuda%20com%20o%20nosso%20servidor%20PHP.%20Empresa%3A%20%E2%80%A6%20Site%3A%20%E2%80%A6

**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:

```bash
# "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.

```ini
# 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:

```bash
sudo systemctl restart php8.3-fpm
systemctl show php8.3-fpm -p MemoryHigh -p MemoryMax -p MemorySwapMax -p OOMPolicy -p MemoryCurrent
```

```ini
; 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](https://phpsos.de/pt/blog/php-admin-value-sperren/).

## 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.

```bash
# 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](https://phpsos.de/pt/blog/memory-limit-richtig-setzen/).

## 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.

```bash
# 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](https://phpsos.de/pt/php2shell/sofortmassnahmen-ohne-patch/).

O que acontece no interior do PHP em caso de falta de memória é explicado em [memory_limit e Out-of-Memory](https://phpsos.de/pt/php/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](https://phpsos.de/pt/leistungen/php-patch-service/), 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

- [systemd.resource-control (MemoryMax, MemoryHigh)](https://www.freedesktop.org/software/systemd/man/latest/systemd.resource-control.html)
- [systemd.service (OOMPolicy)](https://www.freedesktop.org/software/systemd/man/latest/systemd.service.html)
- [php.net: comunicados de segurança](https://www.php.net/)
- [Manual do PHP: memory_limit](https://www.php.net/manual/en/ini.core.php)
