# Fijar límites de memoria con systemd y cgroups

> systemd MemoryMax para PHP-FPM: fijar un límite de memoria con cgroup como recomienda php.net para PHP2Shell, con MemoryHigh, OOMPolicy y comandos de comprobación.

Fuente: https://phpsos.de/es/blog/speichergrenzen-systemd-cgroups/  
Actualizado: 25/10/2026  
Proveedor: PHP SOS, Ayuda de emergencia para PHP y seguridad de servidores. Ayuda inmediata por WhatsApp: https://wa.me/491736494914?text=Hola%20PHP%20SOS%2C%20necesitamos%20ayuda%20con%20nuestro%20servidor%20PHP.%20Empresa%3A%20%E2%80%A6%20Sitio%20web%3A%20%E2%80%A6

**En resumen:** para la medida transitoria de PHP2Shell `memory_limit=-1`, PHP-FPM necesita un límite de memoria fuera de PHP. Bajo systemd se fija con `MemoryHigh=` como freno y `MemoryMax=` como tope duro, en un archivo complementario para el servicio FPM. Según php.net, `ulimit`, `LimitAS=` o `LimitDATA=` no son adecuados para esto. Compruebe además `OOMPolicy=`, para que un solo worker no termine todo el servicio.

## Una recomendación que al principio sorprende

Entre las medidas transitorias que php.net publicó el 21 de octubre de 2026 sobre PHP2Shell hay una que hizo dudar a muchos administradores: `memory_limit=-1`, es decir, ningún límite de memoria en el propio PHP. Durante años se consideró buena práctica justo lo contrario.

Una relación con la descripción de la vulnerabilidad es evidente. En CVE-2026-109618, un estado de falta de memoria (out-of-memory) puede interrumpir PHP mientras los datos de la petición solo están inicializados parcialmente. La medida transitoria traslada el límite de memoria desde PHP al sistema operativo. php.net la vincula expresamente a dos condiciones: el valor debe estar bloqueado frente a la sobrescritura y el límite debe proceder de un contenedor o una cgroup, no de `RLIMIT_AS` ni de `RLIMIT_DATA`.

Quien aplica solo la primera mitad tiene un servidor sin ningún límite de memoria. Este artículo muestra la segunda mitad. Incluso en conjunto, la medida sigue siendo una transición: la vulnerabilidad solo queda corregida con PHP 8.5.12, 8.4.27, 8.3.36, 8.2.35 o un backport confirmado.

## Las cgroups explicadas en dos frases

Los grupos de control, o cgroups, son una función del kernel de Linux que agrupa procesos y limita sus recursos. systemd crea automáticamente una cgroup propia para cada servicio, y todos los procesos del servicio, en el servicio FPM el maestro y todos los workers, pertenecen a ella.

Las opciones de memoria de systemd fijan valores del controlador de memoria de cgroup v2. Si su sistema usa cgroup v2 lo muestra un breve comando:

```bash
# "cgroup2fs" significa: cgroup v2 está activo
stat -fc %T /sys/fs/cgroup/
```

Las versiones actuales de las distribuciones habituales usan cgroup v2 por defecto. Si el comando muestra otra cosa, acláre lo antes del cambio, porque la documentación de systemd describe las antiguas opciones de v1 como obsoletas.

## Fijar systemd MemoryMax para PHP-FPM

El límite va en un archivo complementario, el llamado drop-in. Así el archivo de unidad del paquete permanece sin cambios y una actualización no sobrescribe su ajuste. Los nombres de servicio varían según la distribución; el ejemplo usa Debian y Ubuntu con PHP 8.3.

```ini
# Abre un editor para el 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
```

Las opciones de un vistazo, según la documentación de systemd:

| Opción | Efecto | Indicación para PHP-FPM |
|---|---|---|
| `MemoryHigh=` | límite de estrangulamiento: los procesos se frenan con fuerza y se recupera memoria | la magnitud de control real, algo por debajo de `MemoryMax` |
| `MemoryMax=` | límite absoluto: si no puede mantenerse, el OOM killer se activa en el servicio | última línea de defensa |
| `MemorySwapMax=` | límite para el intercambio | `0` impide que el servicio recurra al swap |
| `OOMPolicy=` | comportamiento tras un evento OOM: `continue`, `stop` o `kill` | `continue` deja que los demás workers sigan funcionando |
| `LimitAS=`, `LimitDATA=` | rlimits por proceso | según php.net, inadecuados para esta medida |

`OOMPolicy=` merece especial atención. Con `stop` o `kill`, tras un evento OOM systemd termina todo el servicio y dejan de funcionar todos los sitios web de ese servicio FPM. Qué valor rige sin indicación propia lo determina el valor por defecto del sistema `DefaultOOMPolicy=`. Por eso compruebe el valor efectivo en lugar de suponerlo.

Después, reinicie el servicio y bloquee `memory_limit` en el pool:

```bash
sudo systemctl restart php8.3-fpm
systemctl show php8.3-fpm -p MemoryHigh -p MemoryMax -p MemorySwapMax -p OOMPolicy -p MemoryCurrent
```

```ini
; Archivo del pool, p. ej. /etc/php/8.3/fpm/pool.d/seite1.conf
php_admin_value[memory_limit] = -1
```

Cómo funciona el bloqueo lo describe [php_admin_value: bloquear ajustes frente a la sobrescritura](https://phpsos.de/es/blog/php-admin-value-sperren/).

## Encontrar el valor correcto

Un límite demasiado ajustado provoca cortes en el funcionamiento normal; uno demasiado holgado apenas protege. El punto de partida es el consumo real.

1. Observar durante varios días la memoria necesaria de todos los procesos FPM, también en los picos de carga.
2. Averiguar la necesidad típica de un worker y multiplicarla por `pm.max_children` de todos los pools del servicio.
3. Prever un margen para el proceso maestro, OPcache y los picos.
4. Fijar `MemoryHigh=` justo por encima del valor máximo medido y `MemoryMax=` con algo de margen por encima.
5. Reajustar los valores al cabo de una semana con base en las mediciones y los eventos OOM.

```bash
# Memoria de todos los procesos PHP-FPM en MB (Resident Set Size)
ps -C php-fpm8.3 -o rss= | awk '{s+=$1} END {printf "%.0f MB\n", s/1024}'
# Resumen continuo de todas las cgroups ordenado por memoria
systemd-cgtop -m
```

Debería anotar el valor de `memory_limit` antes del cambio. Tras la actualización puede volver a un límite fijo en PHP; cómo elegirlo con criterio figura en [Fijar bien memory_limit](https://phpsos.de/es/blog/memory-limit-richtig-setzen/).

## Vigilar: cuándo actúa el límite

Un límite de memoria cuya activación nadie nota es una fuente silenciosa de errores. El kernel cuenta los eventos OOM por cgroup, y systemd y el registro del kernel los anotan.

```bash
# Contador OOM de la cgroup del servicio (ruta con cgroup v2)
cat /sys/fs/cgroup/system.slice/php8.3-fpm.service/memory.events
# Mensajes del OOM killer en el registro del kernel
journalctl -k --since "-1 day" | grep -i "out of memory"
```

Si sube el contador `oom_kill`, el kernel termina workers. Eso puede indicar un límite demasiado ajustado, una aplicación defectuosa o peticiones inusuales. En la situación actual merece la pena cotejar esos eventos con los registros de acceso de la misma hora.

> **Importante:**
> El límite de memoria es una medida transitoria y no sustituye a la actualización de PHP. Planifique la actualización en paralelo y reinicie después todos los procesos de PHP. El resumen de todas las medidas transitorias figura en [Medidas inmediatas si todavía no puede parchear](https://phpsos.de/es/php2shell/sofortmassnahmen-ohne-patch/).

Lo que ocurre dentro de PHP ante la falta de memoria lo explica [memory_limit y Out-of-Memory](https://phpsos.de/es/php/out-of-memory/). En Apache con mod_php rige la misma técnica para el servicio Apache, aunque entonces PHP comparte el límite con el propio servidor web.

## Conclusión: no olvide la segunda mitad

`memory_limit=-1` solo es una medida transitoria defendible junto con un límite externo. systemd hace posible ese límite con pocas líneas: `MemoryHigh` como freno, `MemoryMax` como tope duro, `MemorySwapMax` contra la huida al swap y una `OOMPolicy` elegida con criterio. Mida antes, observe después y tenga presente que todos los pools de un servicio comparten el límite.

Pero sobre todo: planifique el final de la medida. Tras la actualización a una versión corregida de PHP, vuelva a un `memory_limit` fijo. El límite de cgroup puede conservarse como protección adicional contra procesos desbocados.

## ¿Cómo ayuda PHP SOS en concreto?

- **Medir:** registramos la necesidad de memoria de sus servicios PHP y deducimos de ahí los límites adecuados.
- **Aplicar:** fijamos drop-ins con `MemoryHigh`, `MemoryMax` y una `OOMPolicy` adecuada, bloqueamos `memory_limit` en el pool y reiniciamos los servicios de forma controlada.
- **Vigilar:** incorporamos los eventos OOM a la supervisión y reajustamos los valores.
- **Llegar al parche:** mediante el [servicio de parcheo de PHP](https://phpsos.de/es/leistungen/php-patch-service/) instalamos la actualización y retiramos después de forma ordenada los valores transitorios.


## Preguntas frecuentes

**¿Por qué debe estar memory_limit en -1? ¿No es peligroso?**  
Por sí solo lo sería. php.net recomienda memory_limit=-1 como medida transitoria expresamente junto con un límite de memoria mediante contenedor o cgroup. La limitación sale así de PHP y pasa al sistema operativo. Sin este límite externo, no debería fijar el valor.

**¿Puedo usar en su lugar ulimit o LimitAS=?**  
No. php.net desaconseja expresamente RLIMIT_AS y RLIMIT_DATA, y son precisamente estos los límites que fijan ulimit -v, LimitAS= y LimitDATA=. La documentación de systemd advierte además de que LimitAS= limita el espacio de direcciones, no el consumo de memoria.

**¿Qué ocurre cuando PHP-FPM alcanza el límite?**  
Con MemoryHigh, el kernel frena los procesos y recupera memoria. Con MemoryMax, el OOM killer se activa dentro del servicio y termina un proceso, normalmente un worker grande. Si después se detiene todo el servicio lo regula OOMPolicy=.

**¿Rige el límite para cada pool por separado?**  
No. Todos los pools de un servicio FPM comparten una cgroup y, por tanto, un límite común. Los límites separados por sitio web requieren servicios FPM o contenedores separados.

**¿Cómo fijo el límite en Docker o Kubernetes?**  
Mediante la limitación de memoria del contenedor, por ejemplo la opción --memory en docker run o resources.limits.memory en Kubernetes. Ambos usan también cgroups. El principio sigue siendo el mismo: límite fuera de PHP, memory_limit bloqueado en PHP.

## Fuentes

- [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: avisos de seguridad](https://www.php.net/)
- [Manual de PHP: memory_limit](https://www.php.net/manual/en/ini.core.php)
