# Sæt hukommelsesgrænser med systemd og cgroups

> systemd MemoryMax til PHP-FPM: Sæt en hukommelsesgrænse via cgroup, som php.net anbefaler ved PHP2Shell, med MemoryHigh, OOMPolicy og kontrolkommandoer.

Kilde: https://phpsos.de/da/blog/speichergrenzen-systemd-cgroups/  
Status: 25.10.2026  
Udbyder: PHP SOS, PHP-nødhjælp og serversikkerhed. Akut hjælp på WhatsApp: https://wa.me/491736494914?text=Hej%20PHP%20SOS%2C%20vi%20har%20brug%20for%20hj%C3%A6lp%20med%20vores%20PHP-server.%20Firma%3A%20%E2%80%A6%20Website%3A%20%E2%80%A6

**Kort sagt:** Til PHP2Shell-overgangsforanstaltningen `memory_limit=-1` har PHP-FPM brug for en hukommelsesgrænse uden for PHP. Under systemd sætter I den med `MemoryHigh=` som bremse og `MemoryMax=` som hård øvre grænse i en supplerende fil til FPM-tjenesten. `ulimit`, `LimitAS=` eller `LimitDATA=` er ifølge php.net uegnede hertil. Kontrollér desuden `OOMPolicy=`, så en enkelt worker ikke afslutter hele tjenesten.

## En anbefaling, der først vækker undren

Blandt de overgangsforanstaltninger, som php.net offentliggjorde den 21. oktober 2026 til PHP2Shell, står én, der fik mange administratorer til at standse op: `memory_limit=-1`, altså ingen hukommelsesgrænse i selve PHP. I årevis gjaldt det modsatte som god praksis.

En sammenhæng med beskrivelsen af sårbarheden ligger lige for. Ved CVE-2026-109618 kan en out-of-memory-tilstand afbryde PHP, mens forespørgselsdata kun er delvist initialiseret. Overgangsforanstaltningen flytter hukommelsesgrænsen ud af PHP og ind i operativsystemet. php.net knytter den udtrykkeligt til to betingelser: Værdien skal være låst mod overskrivning, og grænsen skal komme via container eller cgroup, ikke via `RLIMIT_AS` eller `RLIMIT_DATA`.

Den, der kun gennemfører første halvdel, har en server uden nogen hukommelsesgrænse. Dette indlæg viser den anden halvdel. Også samlet forbliver foranstaltningen en overgang: Sårbarheden er først rettet med PHP 8.5.12, 8.4.27, 8.3.36, 8.2.35 eller en bekræftet backport.

## cgroups forklaret i to sætninger

Control groups, kort cgroups, er en funktion i Linux-kernen, der grupperer processer og begrænser deres ressourcer. systemd opretter automatisk en egen cgroup til hver tjeneste, og alle tjenestens processer, ved FPM-tjenesten altså master og samtlige workers, hører med.

systemds hukommelsesindstillinger sætter værdier i cgroup-v2-hukommelsescontrolleren. Om jeres system bruger cgroup v2, viser en kort kommando:

```bash
# "cgroup2fs" betyder: cgroup v2 er aktiv
stat -fc %T /sys/fs/cgroup/
```

Aktuelle versioner af de gængse distributioner bruger cgroup v2 som standard. Viser kommandoen noget andet, så afklar det før omstillingen, for systemd-dokumentationen beskriver de gamle v1-indstillinger som forældede.

## Sæt systemd MemoryMax til PHP-FPM

Grænsen kommer i en supplerende fil, et såkaldt drop-in. Så forbliver pakkens unit-fil uændret, og en opdatering overskriver ikke jeres indstilling. Tjenestenavne afviger fra distribution til distribution; eksemplet bruger Debian og Ubuntu med PHP 8.3.

```ini
# Åbner en editor til drop-in'et: sudo systemctl edit php8.3-fpm
# Resultat: /etc/systemd/system/php8.3-fpm.service.d/override.conf
[Service]
MemoryAccounting=yes
MemoryHigh=1536M
MemoryMax=2G
MemorySwapMax=0
OOMPolicy=continue
```

Indstillingerne i overblik, efter systemds dokumentation:

| Indstilling | Virkning | Henvisning for PHP-FPM |
|---|---|---|
| `MemoryHigh=` | drosselgrænse: processer bremses kraftigt, hukommelse hentes tilbage | den egentlige styringsstørrelse, lidt under `MemoryMax` |
| `MemoryMax=` | absolut grænse: kan den ikke holdes, bliver OOM-killeren aktiv i tjenesten | sidste forsvarslinje |
| `MemorySwapMax=` | grænse for udflytning | `0` forhindrer, at tjenesten i stedet viger ud i swap |
| `OOMPolicy=` | adfærd efter en OOM-hændelse: `continue`, `stop` eller `kill` | `continue` lader de øvrige workers køre videre |
| `LimitAS=`, `LimitDATA=` | rlimits pr. proces | ifølge php.net uegnede til denne foranstaltning |

`OOMPolicy=` fortjener særlig opmærksomhed. Ved `stop` eller `kill` afslutter systemd efter en OOM-hændelse hele tjenesten, og alle websites i denne FPM-tjeneste falder ud. Hvilken værdi der gælder uden egen angivelse, fastlægger systemstandarden `DefaultOOMPolicy=`. Kontrollér derfor den virksomme værdi i stedet for at antage den.

Genstart derefter tjenesten, og lås `memory_limit` i poolen:

```bash
sudo systemctl restart php8.3-fpm
systemctl show php8.3-fpm -p MemoryHigh -p MemoryMax -p MemorySwapMax -p OOMPolicy -p MemoryCurrent
```

```ini
; Pool-fil, f.eks. /etc/php/8.3/fpm/pool.d/seite1.conf
php_admin_value[memory_limit] = -1
```

Hvordan låsningen fungerer, beskriver [php_admin_value: Lås indstillinger mod overskrivning](https://phpsos.de/da/blog/php-admin-value-sperren/).

## Find den rigtige værdi

En for snæver grænse giver afbrud i normal drift, en for rundhåndet beskytter næppe. Udgangspunktet er det faktiske forbrug.

1. Observér det aktuelle hukommelsesbehov for alle FPM-processer over nogle dage, også ved belastningstoppe.
2. Find en workers typiske behov, og gang det med `pm.max_children` for alle tjenestens pools.
3. Indregn et tillæg til master-processen, OPcache og spidser.
4. Sæt `MemoryHigh=` lige over den målte spidsværdi, `MemoryMax=` med lidt afstand derover.
5. Finjustér værdierne efter en uge ud fra målingerne og OOM-hændelserne.

```bash
# Hukommelse for alle PHP-FPM-processer i MB (resident set size)
ps -C php-fpm8.3 -o rss= | awk '{s+=$1} END {printf "%.0f MB\n", s/1024}'
# Løbende oversigt over alle cgroups sorteret efter hukommelse
systemd-cgtop -m
```

Værdien for `memory_limit` før omstillingen bør I notere. Efter opdateringen kan I vende tilbage til en fast grænse i PHP; hvordan I vælger den fornuftigt, står under [Sæt memory_limit rigtigt](https://phpsos.de/da/blog/memory-limit-richtig-setzen/).

## Overvåg: Hvornår grænsen virker

En hukommelsesgrænse, hvis udløsning ingen bemærker, er en stille fejlkilde. Kernen tæller OOM-hændelser pr. cgroup, og systemd samt kernelogget noterer dem.

```bash
# OOM-tæller for tjenestens cgroup (sti ved cgroup v2)
cat /sys/fs/cgroup/system.slice/php8.3-fpm.service/memory.events
# Meldinger fra OOM-killeren i kernelogget
journalctl -k --since "-1 day" | grep -i "out of memory"
```

Stiger tælleren `oom_kill`, afslutter kernen workers. Det kan pege på en for snæver grænse, på en fejlbehæftet applikation eller på usædvanlige forespørgsler. I den aktuelle situation er det umagen værd at afstemme sådanne hændelser med adgangsloggene fra samme tidspunkt.

> **Vigtigt:**
> Hukommelsesgrænsen er en overgangsforanstaltning og erstatter ikke PHP-opdateringen. Planlæg opdateringen parallelt, og genstart derefter alle PHP-processer. Alle overgangsforanstaltninger i overblik står under [Akutforanstaltninger, hvis I endnu ikke kan patche](https://phpsos.de/da/php2shell/sofortmassnahmen-ohne-patch/).

Hvad der sker inde i PHP ved hukommelsesmangel, forklarer [memory_limit og out-of-memory](https://phpsos.de/da/php/out-of-memory/). Ved Apache med mod_php gælder den samme teknik for Apache-tjenesten, dog deler PHP så grænsen med selve webserveren.

## Konklusion: Glem ikke den anden halvdel

`memory_limit=-1` er kun sammen med en ydre grænse en forsvarlig overgangsforanstaltning. systemd gør denne grænse mulig med få linjer: `MemoryHigh` som bremse, `MemoryMax` som hård øvre grænse, `MemorySwapMax` mod udvigning til swap og en bevidst valgt `OOMPolicy`. Mål først, observér derefter, og hav in mente, at alle pools i en tjeneste deler grænsen.

Frem for alt: Planlæg foranstaltningens ophør. Efter opdateringen til en rettet PHP-version vender I tilbage til en fast `memory_limit`. Cgroup-grænsen kan I beholde som ekstra sikring mod løbske processer.

## Hvordan hjælper PHP SOS konkret?

- **Mål:** Vi registrerer hukommelsesbehovet i jeres PHP-tjenester og udleder passende grænser deraf.
- **Gennemfør:** Vi sætter drop-ins med `MemoryHigh`, `MemoryMax` og passende `OOMPolicy`, låser `memory_limit` i poolen og genstarter tjenesterne kontrolleret.
- **Observér:** Vi indbinder OOM-hændelser i overvågningen og finjusterer værdierne.
- **Før frem til patchen:** Via [PHP-patch-servicen](https://phpsos.de/da/leistungen/php-patch-service/) indspiller vi opdateringen og tager derefter overgangsværdierne tilbage på ordnet vis.


## Ofte stillede spørgsmål

**Hvorfor skal memory_limit stå på -1? Er det ikke farligt?**  
Alene ville det være det. php.net anbefaler memory_limit=-1 som overgangsforanstaltning udtrykkeligt sammen med en hukommelsesgrænse via container eller cgroup. Begrænsningen flytter dermed ud af PHP og ind i operativsystemet. Uden denne ydre grænse bør I ikke sætte værdien.

**Kan jeg i stedet bruge ulimit eller LimitAS=?**  
Nej. php.net fraråder udtrykkeligt RLIMIT_AS og RLIMIT_DATA, og netop disse grænser sætter ulimit -v, LimitAS= og LimitDATA=. systemd-dokumentationen peger desuden på, at LimitAS= begrænser adresserummet, ikke hukommelsesforbruget.

**Hvad sker der, når PHP-FPM når grænsen?**  
Ved MemoryHigh bremser kernen processerne og henter hukommelse tilbage. Ved MemoryMax bliver OOM-killeren aktiv inde i tjenesten og afslutter en proces, som regel en stor worker. Om hele tjenesten derefter standser, afgør OOMPolicy=.

**Gælder grænsen for hver pool for sig?**  
Nej. Alle pools i en FPM-tjeneste deler en cgroup og dermed en fælles grænse. Adskilte grænser pr. website kræver adskilte FPM-tjenester eller containere.

**Hvordan sætter jeg grænsen i Docker eller Kubernetes?**  
Via containerens hukommelsesbegrænsning, f.eks. tilvalget --memory ved docker run eller resources.limits.memory i Kubernetes. Begge bruger ligeledes cgroups. Grundsætningen er den samme: grænse uden for PHP, memory_limit i PHP låst.

## Kilder

- [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: Sikkerhedsmeddelelser](https://www.php.net/)
- [PHP-manual: memory_limit](https://www.php.net/manual/en/ini.core.php)
