# Sätta minnesgränser med systemd och cgroups

> systemd MemoryMax för PHP-FPM: sätt minnesgräns via cgroup, som php.net rekommenderar för PHP2Shell, med MemoryHigh, OOMPolicy och kontrollkommandon.

Källa: https://phpsos.de/sv/blog/speichergrenzen-systemd-cgroups/  
Aktualitet: 2026-10-25  
Leverantör: PHP SOS, PHP-nödhjälp och serversäkerhet. Omedelbar hjälp via WhatsApp: https://wa.me/491736494914?text=Hej%20PHP%20SOS%2C%20vi%20beh%C3%B6ver%20hj%C3%A4lp%20med%20v%C3%A5r%20PHP-server.%20F%C3%B6retag%3A%20%E2%80%A6%20Webbplats%3A%20%E2%80%A6

**Kort sagt:** För PHP2Shell-övergångsåtgärden `memory_limit=-1` behöver PHP-FPM en minnesgräns utanför PHP. Under systemd sätter ni den med `MemoryHigh=` som broms och `MemoryMax=` som hård övre gräns i en tilläggsfil för FPM-tjänsten. `ulimit`, `LimitAS=` eller `LimitDATA=` är enligt php.net olämpliga för detta. Kontrollera dessutom `OOMPolicy=`, så att en enskild worker inte avslutar hela tjänsten.

## En rekommendation som först väcker förvåning

Bland de övergångsåtgärder som php.net publicerade den 21 oktober 2026 om PHP2Shell finns en som fick många administratörer att haja till: `memory_limit=-1`, alltså ingen minnesgräns i själva PHP. I åratal gällde motsatsen som god praxis.

Ett samband med beskrivningen av sårbarheten ligger nära. Vid CVE-2026-109618 kan ett out-of-memory-tillstånd avbryta PHP medan förfrågningsdata bara är delvis initierade. Övergångsåtgärden flyttar minnesgränsen ut ur PHP och in i operativsystemet. php.net knyter den uttryckligen till två villkor: värdet måste vara spärrat mot överskrivning, och gränsen måste komma via container eller cgroup, inte via `RLIMIT_AS` eller `RLIMIT_DATA`.

Den som bara genomför den första hälften har en server utan någon minnesgräns alls. Det här inlägget visar den andra hälften. Även tillsammans förblir åtgärden en övergång: sårbarheten är åtgärdad först med PHP 8.5.12, 8.4.27, 8.3.36, 8.2.35 eller en bekräftad backport.

## cgroups förklarade i två meningar

Control groups, förkortat cgroups, är en funktion i Linux-kärnan som grupperar processer och begränsar deras resurser. systemd skapar automatiskt en egen cgroup för varje tjänst, och alla tjänstens processer, vid FPM-tjänsten alltså master och samtliga workers, hör dit.

systemds minnesalternativ sätter värden i cgroup-v2-minneskontrollern. Om ert system använder cgroup v2 visar ett kort kommando:

```bash
# "cgroup2fs" betyder: cgroup v2 är aktivt
stat -fc %T /sys/fs/cgroup/
```

Aktuella versioner av de vanliga distributionerna använder cgroup v2 som standard. Visar kommandot något annat bör ni klargöra det före omställningen, eftersom systemd-dokumentationen beskriver de gamla v1-alternativen som föråldrade.

## Sätta systemd MemoryMax för PHP-FPM

Gränsen läggs i en tilläggsfil, en så kallad drop-in. Då förblir paketets unit-fil oförändrad, och en uppdatering skriver inte över er inställning. Tjänstenamn skiljer sig mellan distributioner; exemplet använder Debian och Ubuntu med PHP 8.3.

```ini
# Öppnar en editor för drop-in: 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
```

Alternativen i överblick, enligt systemds dokumentation:

| Alternativ | Verkan | Anmärkning för PHP-FPM |
|---|---|---|
| `MemoryHigh=` | Strypgräns: processer bromsas kraftigt, minne tas tillbaka | den egentliga styrstorleken, något under `MemoryMax` |
| `MemoryMax=` | absolut gräns: går den inte att hålla blir OOM-killern aktiv i tjänsten | sista försvarslinjen |
| `MemorySwapMax=` | gräns för utlagring | `0` förhindrar att tjänsten i stället väjer ut i swap |
| `OOMPolicy=` | beteende efter en OOM-händelse: `continue`, `stop` eller `kill` | `continue` låter övriga workers fortsätta köra |
| `LimitAS=`, `LimitDATA=` | rlimits per process | enligt php.net olämpliga för den här åtgärden |

`OOMPolicy=` förtjänar särskild uppmärksamhet. Vid `stop` eller `kill` avslutar systemd efter en OOM-händelse hela tjänsten, och alla webbplatser i den FPM-tjänsten slås ut. Vilket värde som gäller utan egen uppgift avgör systemstandarden `DefaultOOMPolicy=`. Kontrollera därför det verksamma värdet i stället för att anta det.

Starta därefter om tjänsten och spärra `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
; Poolfil, t.ex. /etc/php/8.3/fpm/pool.d/seite1.conf
php_admin_value[memory_limit] = -1
```

Hur spärrandet fungerar beskriver [php_admin_value: spärra inställningar mot överskrivning](https://phpsos.de/sv/blog/php-admin-value-sperren/).

## Hitta rätt värde

En för snäv gräns ger avbrott i normaldriften, en för generös skyddar knappt. Utgångspunkten är den faktiska förbrukningen.

1. Observera det aktuella minnesbehovet för alla FPM-processer under några dagar, även vid belastningstoppar.
2. Ta reda på det typiska behovet för en worker och multiplicera med `pm.max_children` för alla poolerna i tjänsten.
3. Räkna in ett påslag för masterprocessen, OPcache och toppar.
4. Sätt `MemoryHigh=` strax över det uppmätta toppvärdet, `MemoryMax=` med lite marginal ovanför.
5. Justera värdena efter en vecka utifrån mätningarna och OOM-händelserna.

```bash
# Minne för alla 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öpande översikt över alla cgroups sorterade efter minne
systemd-cgtop -m
```

Anteckna värdet för `memory_limit` före omställningen. Efter uppdateringen kan ni återgå till en fast gräns i PHP; hur ni väljer den förnuftigt står under [Ställ in memory_limit rätt](https://phpsos.de/sv/blog/memory-limit-richtig-setzen/).

## Övervaka: när gränsen slår till

En minnesgräns vars utlösande ingen märker är en tyst felkälla. Kärnan räknar OOM-händelser per cgroup, och systemd samt kärnloggen noterar dem.

```bash
# OOM-räknare för tjänstens cgroup (sökväg vid cgroup v2)
cat /sys/fs/cgroup/system.slice/php8.3-fpm.service/memory.events
# Meddelanden från OOM-killern i kärnloggen
journalctl -k --since "-1 day" | grep -i "out of memory"
```

Stiger räknaren `oom_kill` avslutar kärnan workers. Det kan tyda på en för snäv gräns, på en felaktig applikation eller på ovanliga förfrågningar. I det aktuella läget lönar det sig att jämföra sådana händelser med åtkomstloggarna för samma tidpunkt.

> **Viktigt:**
> Minnesgränsen är en övergångsåtgärd och ersätter inte PHP-uppdateringen. Planera uppdateringen parallellt och starta därefter om alla PHP-processer. Alla övergångsåtgärder i överblick finns under [Akuta åtgärder när ni ännu inte kan patcha](https://phpsos.de/sv/php2shell/sofortmassnahmen-ohne-patch/).

Vad som händer inuti PHP vid minnesbrist förklarar [memory_limit och out-of-memory](https://phpsos.de/sv/php/out-of-memory/). Med Apache och mod_php gäller samma teknik för Apache-tjänsten, men då delar PHP gränsen med själva webbservern.

## Slutsats: glöm inte den andra hälften

`memory_limit=-1` är bara tillsammans med en yttre gräns en försvarbar övergångsåtgärd. systemd gör den gränsen möjlig med några få rader: `MemoryHigh` som broms, `MemoryMax` som hård övre gräns, `MemorySwapMax` mot att väja ut i swap och en medvetet vald `OOMPolicy`. Mät i förväg, observera efteråt och ha i åtanke att alla pooler i en tjänst delar gränsen.

Framför allt: planera åtgärdens slut. Efter uppdateringen till en rättad PHP-version återgår ni till en fast `memory_limit`. Cgroup-gränsen kan ni behålla som extra säkring mot skenande processer.

## Hur hjälper PHP SOS konkret?

- **Mäta:** Vi kartlägger minnesbehovet för era PHP-tjänster och härleder lämpliga gränser.
- **Genomföra:** Vi sätter drop-ins med `MemoryHigh`, `MemoryMax` och lämplig `OOMPolicy`, spärrar `memory_limit` i poolen och startar om tjänsterna kontrollerat.
- **Observera:** Vi bygger in OOM-händelser i övervakningen och justerar värdena.
- **Leda till patchen:** Via [PHP-patchtjänsten](https://phpsos.de/sv/leistungen/php-patch-service/) installerar vi uppdateringen och tar därefter tillbaka övergångsvärdena ordnat.


## Vanliga frågor

**Varför ska memory_limit stå på -1? Är inte det farligt?**  
Ensamt vore det det. php.net rekommenderar memory_limit=-1 som övergångsåtgärd uttryckligen tillsammans med en minnesgräns via container eller cgroup. Begränsningen flyttar då ut ur PHP och in i operativsystemet. Utan den yttre gränsen bör ni inte sätta värdet.

**Kan jag i stället använda ulimit eller LimitAS=?**  
Nej. php.net avråder uttryckligen från RLIMIT_AS och RLIMIT_DATA, och just dessa gränser sätter ulimit -v, LimitAS= och LimitDATA=. systemd-dokumentationen påpekar dessutom att LimitAS= begränsar adressutrymmet, inte minnesförbrukningen.

**Vad händer när PHP-FPM når gränsen?**  
Vid MemoryHigh bromsar kärnan processerna och tar tillbaka minne. Vid MemoryMax blir OOM-killern aktiv inom tjänsten och avslutar en process, oftast en stor worker. Om hela tjänsten stoppas därefter styr OOMPolicy=.

**Gäller gränsen för varje pool för sig?**  
Nej. Alla pooler i en FPM-tjänst delar en cgroup och därmed en gemensam gräns. Separata gränser per webbplats kräver separata FPM-tjänster eller containrar.

**Hur sätter jag gränsen i Docker eller Kubernetes?**  
Via containerns minnesbegränsning, till exempel alternativet --memory vid docker run eller resources.limits.memory i Kubernetes. Båda använder också cgroups. Principen är densamma: gräns utanför PHP, memory_limit spärrat i PHP.

## Källor

- [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: säkerhetsmeddelanden](https://www.php.net/)
- [PHP-manualen: memory_limit](https://www.php.net/manual/en/ini.core.php)
