# Speichergrenzen mit systemd und cgroups setzen

> systemd MemoryMax für PHP-FPM: Speichergrenze per cgroup setzen, wie php.net es zu PHP2Shell empfiehlt, mit MemoryHigh, OOMPolicy und Prüfbefehlen.

Quelle: https://phpsos.de/blog/speichergrenzen-systemd-cgroups/  
Stand: 25.10.2026  
Anbieter: PHP SOS, PHP-Notfallhilfe und Server-Sicherheit. Soforthilfe per WhatsApp: https://wa.me/491736494914?text=Hallo%20PHP%20SOS%2C%20wir%20brauchen%20Hilfe%20mit%20unserem%20PHP-Server.%20Firma%3A%20%E2%80%A6%20Website%3A%20%E2%80%A6

**Kurz gesagt:** Für die PHP2Shell-Übergangsmaßnahme `memory_limit=-1` braucht PHP-FPM eine Speichergrenze außerhalb von PHP. Unter systemd setzen Sie sie mit `MemoryHigh=` als Bremse und `MemoryMax=` als harte Obergrenze in einer Ergänzungsdatei für den FPM-Dienst. `ulimit`, `LimitAS=` oder `LimitDATA=` sind dafür laut php.net ungeeignet. Prüfen Sie außerdem `OOMPolicy=`, damit ein einzelner Worker nicht den ganzen Dienst beendet.

## Eine Empfehlung, die erst einmal verwundert

Unter den Übergangsmaßnahmen, die php.net am 21. Oktober 2026 zu PHP2Shell veröffentlicht hat, steht eine, die viele Administratoren stutzen ließ: `memory_limit=-1`, also keine Speichergrenze in PHP selbst. Jahrelang galt das Gegenteil als gute Praxis.

Ein Zusammenhang mit der Beschreibung der Lücke liegt nahe. Bei CVE-2026-109618 kann ein Out-of-Memory-Zustand PHP unterbrechen, während Anfragedaten nur teilweise initialisiert sind. Die Übergangsmaßnahme verlagert die Speichergrenze aus PHP heraus in das Betriebssystem. php.net verbindet sie ausdrücklich mit zwei Bedingungen: Der Wert muss gegen Überschreiben gesperrt sein, und die Grenze muss über Container oder cgroup kommen, nicht über `RLIMIT_AS` oder `RLIMIT_DATA`.

Wer nur die erste Hälfte umsetzt, hat einen Server ohne jede Speichergrenze. Dieser Beitrag zeigt die zweite Hälfte. Auch zusammen bleibt die Maßnahme ein Übergang: Behoben ist die Lücke erst mit PHP 8.5.12, 8.4.27, 8.3.36, 8.2.35 oder einem bestätigten Backport.

## cgroups in zwei Sätzen erklärt

Control Groups, kurz cgroups, sind eine Funktion des Linux-Kernels, die Prozesse gruppiert und deren Ressourcen begrenzt. systemd legt für jeden Dienst automatisch eine eigene cgroup an, und alle Prozesse des Dienstes, beim FPM-Dienst also Master und sämtliche Worker, gehören dazu.

Die Speicheroptionen von systemd setzen Werte des cgroup-v2-Speichercontrollers. Ob Ihr System cgroup v2 nutzt, zeigt ein kurzer Befehl:

```bash
# "cgroup2fs" bedeutet: cgroup v2 ist aktiv
stat -fc %T /sys/fs/cgroup/
```

Aktuelle Versionen der gängigen Distributionen nutzen cgroup v2 standardmäßig. Zeigt der Befehl etwas anderes, klären Sie das vor der Umstellung, denn die systemd-Dokumentation beschreibt die alten v1-Optionen als veraltet.

## systemd MemoryMax für PHP-FPM setzen

Die Grenze kommt in eine Ergänzungsdatei, ein sogenanntes Drop-in. So bleibt die Unit-Datei des Pakets unverändert, und ein Update überschreibt Ihre Einstellung nicht. Dienstnamen weichen je Distribution ab; das Beispiel nutzt Debian und Ubuntu mit PHP 8.3.

```ini
# Öffnet einen Editor für das Drop-in: sudo systemctl edit php8.3-fpm
# Ergebnis: /etc/systemd/system/php8.3-fpm.service.d/override.conf
[Service]
MemoryAccounting=yes
MemoryHigh=1536M
MemoryMax=2G
MemorySwapMax=0
OOMPolicy=continue
```

Die Optionen im Überblick, nach der Dokumentation von systemd:

| Option | Wirkung | Hinweis für PHP-FPM |
|---|---|---|
| `MemoryHigh=` | Drosselgrenze: Prozesse werden stark gebremst, Speicher wird zurückgeholt | die eigentliche Steuergröße, etwas unter `MemoryMax` |
| `MemoryMax=` | absolute Grenze: ist sie nicht zu halten, wird der OOM-Killer im Dienst aktiv | letzte Verteidigungslinie |
| `MemorySwapMax=` | Grenze für Auslagerung | `0` verhindert, dass der Dienst stattdessen in den Swap ausweicht |
| `OOMPolicy=` | Verhalten nach einem OOM-Ereignis: `continue`, `stop` oder `kill` | `continue` lässt die übrigen Worker weiterlaufen |
| `LimitAS=`, `LimitDATA=` | rlimits pro Prozess | laut php.net für diese Maßnahme ungeeignet |

`OOMPolicy=` verdient besondere Aufmerksamkeit. Bei `stop` oder `kill` beendet systemd nach einem OOM-Ereignis den gesamten Dienst, und alle Websites dieses FPM-Dienstes fallen aus. Welcher Wert ohne eigene Angabe gilt, legt der Systemstandard `DefaultOOMPolicy=` fest. Prüfen Sie den wirksamen Wert deshalb, statt ihn anzunehmen.

Danach den Dienst neu starten und im Pool `memory_limit` sperren:

```bash
sudo systemctl restart php8.3-fpm
systemctl show php8.3-fpm -p MemoryHigh -p MemoryMax -p MemorySwapMax -p OOMPolicy -p MemoryCurrent
```

```ini
; Pool-Datei, z. B. /etc/php/8.3/fpm/pool.d/seite1.conf
php_admin_value[memory_limit] = -1
```

Wie das Sperren funktioniert, beschreibt [php_admin_value: Einstellungen gegen Überschreiben sperren](https://phpsos.de/blog/php-admin-value-sperren/).

## Den richtigen Wert finden

Eine zu knappe Grenze erzeugt Abbrüche im Normalbetrieb, eine zu großzügige schützt kaum. Ausgangspunkt ist der tatsächliche Verbrauch.

1. Den aktuellen Speicherbedarf aller FPM-Prozesse über einige Tage beobachten, auch zu Lastspitzen.
2. Den typischen Bedarf eines Workers ermitteln und mit `pm.max_children` aller Pools des Dienstes multiplizieren.
3. Einen Aufschlag für den Master-Prozess, OPcache und Spitzen einplanen.
4. `MemoryHigh=` knapp über dem gemessenen Spitzenwert setzen, `MemoryMax=` mit etwas Abstand darüber.
5. Werte nach einer Woche anhand der Messungen und OOM-Ereignisse nachjustieren.

```bash
# Speicher aller PHP-FPM-Prozesse in MB (Resident Set Size)
ps -C php-fpm8.3 -o rss= | awk '{s+=$1} END {printf "%.0f MB\n", s/1024}'
# Laufende Übersicht aller cgroups nach Speicher sortiert
systemd-cgtop -m
```

Den Wert für `memory_limit` vor der Umstellung sollten Sie notieren. Nach dem Update können Sie zu einer festen Grenze in PHP zurückkehren; wie Sie diese sinnvoll wählen, steht unter [memory_limit richtig setzen](https://phpsos.de/blog/memory-limit-richtig-setzen/).

## Überwachen: Wann die Grenze greift

Eine Speichergrenze, deren Auslösen niemand bemerkt, ist eine stille Fehlerquelle. Der Kernel zählt OOM-Ereignisse je cgroup, und systemd sowie das Kernel-Protokoll vermerken sie.

```bash
# OOM-Zähler der cgroup des Dienstes (Pfad bei cgroup v2)
cat /sys/fs/cgroup/system.slice/php8.3-fpm.service/memory.events
# Meldungen des OOM-Killers im Kernel-Protokoll
journalctl -k --since "-1 day" | grep -i "out of memory"
```

Steigt der Zähler `oom_kill`, beendet der Kernel Worker. Das kann auf eine zu knappe Grenze hinweisen, auf eine fehlerhafte Anwendung oder auf ungewöhnliche Anfragen. In der aktuellen Lage lohnt es, solche Ereignisse mit den Zugriffsprotokollen zur selben Zeit abzugleichen.

> **Wichtig:**
> Die Speichergrenze ist eine Übergangsmaßnahme und ersetzt nicht das PHP-Update. Planen Sie das Update parallel, und starten Sie danach alle PHP-Prozesse neu. Alle Übergangsmaßnahmen im Überblick stehen unter [Sofortmaßnahmen, wenn Sie noch nicht patchen können](https://phpsos.de/php2shell/sofortmassnahmen-ohne-patch/).

Was im Inneren von PHP bei Speichermangel passiert, erklärt [memory_limit und Out-of-Memory](https://phpsos.de/php/out-of-memory/). Bei Apache mit mod_php gilt dieselbe Technik für den Apache-Dienst, allerdings teilt sich PHP die Grenze dann mit dem Webserver selbst.

## Fazit: Die zweite Hälfte nicht vergessen

`memory_limit=-1` ist nur zusammen mit einer äußeren Grenze eine vertretbare Übergangsmaßnahme. systemd macht diese Grenze mit wenigen Zeilen möglich: `MemoryHigh` als Bremse, `MemoryMax` als harte Obergrenze, `MemorySwapMax` gegen das Ausweichen in den Swap und eine bewusst gewählte `OOMPolicy`. Messen Sie vorher, beobachten Sie danach, und behalten Sie im Blick, dass alle Pools eines Dienstes sich die Grenze teilen.

Vor allem aber: Planen Sie das Ende der Maßnahme. Nach dem Update auf eine behobene PHP-Version kehren Sie zu einem festen `memory_limit` zurück. Die cgroup-Grenze können Sie als zusätzliche Absicherung gegen ausufernde Prozesse behalten.

## Wie hilft PHP SOS konkret?

- **Messen:** Wir erfassen den Speicherbedarf Ihrer PHP-Dienste und leiten daraus passende Grenzen ab.
- **Umsetzen:** Wir setzen Drop-ins mit `MemoryHigh`, `MemoryMax` und passender `OOMPolicy`, sperren `memory_limit` im Pool und starten die Dienste kontrolliert neu.
- **Beobachten:** OOM-Ereignisse binden wir in die Überwachung ein und justieren die Werte nach.
- **Zum Patch führen:** Über den [PHP-Patch-Service](https://phpsos.de/leistungen/php-patch-service/) spielen wir das Update ein und nehmen die Übergangswerte danach geordnet zurück.


## Häufige Fragen

**Warum soll memory_limit auf -1 stehen? Ist das nicht gefährlich?**  
Allein wäre es das. php.net empfiehlt memory_limit=-1 als Übergangsmaßnahme ausdrücklich zusammen mit einer Speichergrenze über Container oder cgroup. Die Begrenzung wandert damit aus PHP heraus in das Betriebssystem. Ohne diese äußere Grenze sollten Sie den Wert nicht setzen.

**Kann ich stattdessen ulimit oder LimitAS= verwenden?**  
Nein. php.net rät ausdrücklich von RLIMIT_AS und RLIMIT_DATA ab, und genau diese Grenzen setzen ulimit -v, LimitAS= und LimitDATA=. Die systemd-Dokumentation weist zudem darauf hin, dass LimitAS= den Adressraum begrenzt, nicht den Speicherverbrauch.

**Was passiert, wenn PHP-FPM die Grenze erreicht?**  
Bei MemoryHigh bremst der Kernel die Prozesse und holt Speicher zurück. Bei MemoryMax wird innerhalb des Dienstes der OOM-Killer aktiv und beendet einen Prozess, meist einen großen Worker. Ob danach der ganze Dienst stoppt, regelt OOMPolicy=.

**Gilt die Grenze für jeden Pool einzeln?**  
Nein. Alle Pools eines FPM-Dienstes teilen sich eine cgroup und damit eine gemeinsame Grenze. Getrennte Grenzen je Website brauchen getrennte FPM-Dienste oder Container.

**Wie setze ich die Grenze in Docker oder Kubernetes?**  
Über die Speicherbegrenzung des Containers, etwa die Option --memory bei docker run oder resources.limits.memory in Kubernetes. Beide nutzen ebenfalls cgroups. Der Grundsatz bleibt gleich: Grenze außerhalb von PHP, memory_limit in PHP gesperrt.

## Quellen

- [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: Sicherheitsmeldungen](https://www.php.net/)
- [PHP-Handbuch: memory_limit](https://www.php.net/manual/en/ini.core.php)
