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:
# "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.
# Å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:
sudo systemctl restart php8.3-fpm
systemctl show php8.3-fpm -p MemoryHigh -p MemoryMax -p MemorySwapMax -p OOMPolicy -p MemoryCurrent
; 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.
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.
- Observér det aktuelle hukommelsesbehov for alle FPM-processer over nogle dage, også ved belastningstoppe.
- Find en workers typiske behov, og gang det med
pm.max_childrenfor alle tjenestens pools. - Indregn et tillæg til master-processen, OPcache og spidser.
- Sæt
MemoryHigh=lige over den målte spidsværdi,MemoryMax=med lidt afstand derover. - Finjustér værdierne efter en uge ud fra målingerne og OOM-hændelserne.
# 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.
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.
# 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.
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.
Hvad der sker inde i PHP ved hukommelsesmangel, forklarer memory_limit og 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,MemoryMaxog passendeOOMPolicy, låsermemory_limiti 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 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.