Na kratko: Za prehodni ukrep PHP2Shell memory_limit=-1 PHP-FPM potrebuje pomnilniško mejo zunaj PHP. Pod systemd jo nastavite z MemoryHigh= kot zavoro in MemoryMax= kot trdo zgornjo mejo v dopolnilni datoteki za storitev FPM. ulimit, LimitAS= ali LimitDATA= so za to po php.net neprimerni. Preverite še OOMPolicy=, da en sam delavec ne konča celotne storitve.
Priporočilo, ki sprva preseneti
Med prehodnimi ukrepi, ki jih je php.net 21. oktobra 2026 objavil za PHP2Shell, je eden, ki je številne skrbnike presenetil: memory_limit=-1, torej brez pomnilniške meje v samem PHP. Leta je veljalo nasprotno kot dobra praksa.
Povezava z opisom ranljivosti je očitna. Pri CVE-2026-109618 lahko stanje brez pomnilnika (out-of-memory) prekine PHP, medtem ko so podatki zahteve inicializirani le delno. Prehodni ukrep pomnilniško mejo iz PHP preseli v operacijski sistem. php.net ga izrecno veže na dva pogoja: vrednost mora biti zaklenjena pred prepisovanjem, meja pa mora priti prek vsebnika ali cgroup, ne prek RLIMIT_AS ali RLIMIT_DATA.
Kdor izvede samo prvo polovico, ima strežnik brez kakršne koli pomnilniške meje. Ta prispevek pokaže drugo polovico. Tudi skupaj ukrep ostaja prehod: ranljivost je odpravljena šele s PHP 8.5.12, 8.4.27, 8.3.36, 8.2.35 ali potrjenim backportom.
cgroups v dveh stavkih
Control Groups, na kratko cgroups, so funkcija jedra Linux, ki procese združuje v skupine in omejuje njihove vire. systemd za vsako storitev samodejno ustvari lasten cgroup in vsi procesi storitve, pri storitvi FPM torej master in vsi delavci, mu pripadajo.
Pomnilniške možnosti systemd nastavljajo vrednosti krmilnika pomnilnika cgroup v2. Ali vaš sistem uporablja cgroup v2, pokaže kratek ukaz:
# "cgroup2fs" pomeni: cgroup v2 je aktiven
stat -fc %T /sys/fs/cgroup/
Aktualne različice običajnih distribucij privzeto uporabljajo cgroup v2. Če ukaz pokaže kaj drugega, to razjasnite pred prehodom, saj dokumentacija systemd stare možnosti v1 opisuje kot zastarele.
Nastavitev systemd MemoryMax za PHP-FPM
Meja pride v dopolnilno datoteko, tako imenovani drop-in. Tako datoteka enote paketa ostane nespremenjena in posodobitev vaše nastavitve ne prepiše. Imena storitev se po distribuciji razlikujejo; primer uporablja Debian in Ubuntu s PHP 8.3.
# Odpre urejevalnik za drop-in: sudo systemctl edit php8.3-fpm
# Rezultat: /etc/systemd/system/php8.3-fpm.service.d/override.conf
[Service]
MemoryAccounting=yes
MemoryHigh=1536M
MemoryMax=2G
MemorySwapMax=0
OOMPolicy=continue
Možnosti v pregledu, po dokumentaciji systemd:
| Možnost | Učinek | Namig za PHP-FPM |
|---|---|---|
MemoryHigh= |
meja dušenja: procesi se močno zavirajo, pomnilnik se vrača | pravi krmilni parameter, nekoliko pod MemoryMax |
MemoryMax= |
absolutna meja: če je ni mogoče držati, se v storitvi aktivira OOM-killer | zadnja obrambna linija |
MemorySwapMax= |
meja za izmenjavo | 0 prepreči, da bi storitev namesto tega zaplavala v swap |
OOMPolicy= |
obnašanje po dogodku OOM: continue, stop ali kill |
continue pusti preostale delavce teči naprej |
LimitAS=, LimitDATA= |
rlimiti na proces | po php.net za ta ukrep neprimerni |
OOMPolicy= si zasluži posebno pozornost. Pri stop ali kill systemd po dogodku OOM konča celotno storitev in vse spletne strani te storitve FPM odpovedo. Katera vrednost velja brez lastne navedbe, določa sistemska privzeta vrednost DefaultOOMPolicy=. Veljavno vrednost zato preverite, namesto da jo predpostavljate.
Nato storitev znova zaženite in v poolu zaklenite memory_limit:
sudo systemctl restart php8.3-fpm
systemctl show php8.3-fpm -p MemoryHigh -p MemoryMax -p MemorySwapMax -p OOMPolicy -p MemoryCurrent
; Datoteka poola, npr. /etc/php/8.3/fpm/pool.d/seite1.conf
php_admin_value[memory_limit] = -1
Kako zaklepanje deluje, opisuje php_admin_value: zaklepanje nastavitev pred prepisovanjem.
Iskanje prave vrednosti
Prekratka meja povzroča prekinitve v običajnem delovanju, preveč radodarna komaj ščiti. Izhodišče je dejanska poraba.
- Opazujte trenutno pomnilniško potrebo vseh procesov FPM več dni, tudi ob konicah obremenitve.
- Ugotovite tipično potrebo delavca in jo pomnožite s
pm.max_childrenvseh poolov storitve. - Načrtujte pribitek za proces master, OPcache in konice.
MemoryHigh=nastavite tik nad izmerjeno konico,MemoryMax=z nekaj razmika nad tem.- Vrednosti po enem tednu popravite glede na meritve in dogodke OOM.
# Pomnilnik vseh procesov PHP-FPM v MB (Resident Set Size)
ps -C php-fpm8.3 -o rss= | awk '{s+=$1} END {printf "%.0f MB\n", s/1024}'
# Tekoči pregled vseh cgroups, razvrščen po pomnilniku
systemd-cgtop -m
Vrednost za memory_limit si pred prehodom zabeležite. Po posodobitvi se lahko vrnete k fiksni meji v PHP; kako jo smiselno izberete, piše pod Pravilna nastavitev memory_limit.
Spremljanje: kdaj meja deluje
Pomnilniška meja, katere sprožitve nihče ne opazi, je tihi vir napak. Jedro šteje dogodke OOM za vsak cgroup, systemd in dnevnik jedra pa jih zabeležita.
# Števec OOM cgroupa storitve (pot pri cgroup v2)
cat /sys/fs/cgroup/system.slice/php8.3-fpm.service/memory.events
# Sporočila OOM-killerja v dnevniku jedra
journalctl -k --since "-1 day" | grep -i "out of memory"
Če števec oom_kill narašča, jedro končuje delavce. To lahko kaže na prekratko mejo, napačno aplikacijo ali nenavadne zahteve. V trenutnih razmerah se take dogodke splača primerjati z dostopnimi dnevniki ob istem času.
Pomnilniška meja je prehodni ukrep in posodobitve PHP ne nadomešča. Posodobitev načrtujte vzporedno in nato znova zaženite vse procese PHP. Vsi prehodni ukrepi v pregledu so pod Takojšnji ukrepi, če popravka še ne morete namestiti.
Kaj se ob pomanjkanju pomnilnika dogaja v notranjosti PHP, pojasnjuje memory_limit in out-of-memory. Pri Apache z mod_php enaka tehnika velja za storitev Apache, vendar si PHP potem mejo deli s samim spletnim strežnikom.
Sklep: druge polovice ne pozabite
memory_limit=-1 je upravičen prehodni ukrep samo skupaj z zunanjo mejo. systemd to mejo omogoči z nekaj vrsticami: MemoryHigh kot zavora, MemoryMax kot trda zgornja meja, MemorySwapMax proti umiku v swap in zavestno izbran OOMPolicy. Izmerite vnaprej, opazujte potem in imejte v mislih, da si vsi pooli ene storitve mejo delijo.
Predvsem pa: načrtujte konec ukrepa. Po posodobitvi na popravljeno različico PHP se vrnite k fiksnemu memory_limit. Mejo cgroup lahko obdržite kot dodatno zaščito pred ušlimi procesi.
Kako PHP SOS pomaga konkretno?
- Merjenje: zajamemo pomnilniško potrebo vaših storitev PHP in iz tega izpeljemo ustrezne meje.
- Izvedba: nastavimo drop-ine z
MemoryHigh,MemoryMaxin ustreznimOOMPolicy, v poolu zaklenemomemory_limitin storitve nadzorovano znova zaženemo. - Opazovanje: dogodke OOM vključimo v spremljanje in vrednosti popravimo.
- Pot do popravka: prek storitve popravljanja PHP namestimo posodobitev in nato prehodne vrednosti urejeno umaknemo.
Pogosta vprašanja
Zakaj naj bo memory_limit nastavljen na -1? Ali ni to nevarno?
Samo po sebi bi bilo. php.net memory_limit=-1 kot prehodni ukrep izrecno priporoča skupaj z mejo pomnilnika prek vsebnika ali cgroup. Omejitev se tako iz PHP preseli v operacijski sistem. Brez te zunanje meje vrednosti ne nastavljajte.
Ali lahko namesto tega uporabim ulimit ali LimitAS=?
Ne. php.net izrecno odsvetuje RLIMIT_AS in RLIMIT_DATA, prav ti meji pa nastavljajo ulimit -v, LimitAS= in LimitDATA=. Dokumentacija systemd poleg tega opozarja, da LimitAS= omejuje naslovni prostor, ne porabe pomnilnika.
Kaj se zgodi, ko PHP-FPM doseže mejo?
Pri MemoryHigh jedro procese zavira in vrača pomnilnik. Pri MemoryMax se znotraj storitve aktivira OOM-killer in konča en proces, običajno velikega delavca. Ali se potem ustavi celotna storitev, ureja OOMPolicy=.
Ali meja velja za vsak pool posebej?
Ne. Vsi pooli ene storitve FPM si delijo en cgroup in s tem skupno mejo. Ločene meje za vsako spletno stran zahtevajo ločene storitve FPM ali vsebnike.
Kako mejo nastavim v Dockerju ali Kubernetesu?
Prek omejitve pomnilnika vsebnika, na primer z možnostjo --memory pri docker run ali resources.limits.memory v Kubernetesu. Oba prav tako uporabljata cgroups. Načelo ostaja enako: meja zunaj PHP, memory_limit v PHP zaklenjen.