Ajankohtainen tilanne: PHP2Shell. Kaksi kriittistä PHP-haavoittuvuutta (CVSS 9.2) sallii haittakoodin suorittamisen ilman kirjautumista. Korjaukset 21.10.2026 alkaen.Mitä nyt on tehtävä →

Muistirajojen asettaminen systemd:llä ja cgroupeilla

php.net suosittelee PHP2Shellin siirtymäajan toimenpiteenä asetusta memory_limit=-1 ja PHP:n ulkopuolista muistirajaa. Näin asetatte rajan systemd:llä ja cgroupeilla niin, ettei yksittäinen worker vedä koko palvelua mukanaan.

Tilanne Lukuaika 5 minKoventaminen ja asetukset

Lyhyesti: PHP2Shellin siirtymäajan toimenpide memory_limit=-1 vaatii PHP-FPM:lle muistirajan PHP:n ulkopuolelle. systemd:n alla asetatte sen arvolla MemoryHigh= jarruna ja arvolla MemoryMax= kovana ylärajana FPM-palvelun täydennystiedostossa. ulimit, LimitAS= tai LimitDATA= ovat tähän php.net:n mukaan sopimattomia. Tarkistakaa lisäksi OOMPolicy=, jotta yksittäinen worker ei lopeta koko palvelua.

Suositus, joka aluksi hämmästyttää

Siirtymäajan toimenpiteiden joukossa, jotka php.net julkaisi PHP2Shellistä 21. lokakuuta 2026, on yksi, joka sai monet ylläpitäjät pysähtymään: memory_limit=-1, eli ei muistirajaa PHP:ssä itsessään. Vuosia päinvastaista pidettiin hyvänä käytäntönä.

Yhteys aukon kuvaukseen on ilmeinen. CVE-2026-109618:ssa muistin loppuminen voi keskeyttää PHP:n, kun pyynnön tiedot on alustettu vain osittain. Siirtymäajan toimenpide siirtää muistirajan PHP:stä käyttöjärjestelmään. php.net sitoo sen nimenomaisesti kahteen ehtoon: arvon on oltava lukittu ylikirjoitukselta, ja rajan on tultava kontin tai cgroupin kautta, ei RLIMIT_AS- tai RLIMIT_DATA-rajojen kautta.

Joka toteuttaa vain ensimmäisen puolikkaan, saa palvelimen ilman minkäänlaista muistirajaa. Tämä kirjoitus näyttää toisen puolikkaan. Myös yhdessä toimenpide on siirtymä: aukko on korjattu vasta versiolla PHP 8.5.12, 8.4.27, 8.3.36, 8.2.35 tai vahvistetulla backportilla.

cgroupit kahdessa lauseessa

Control Groups, lyhyesti cgroupit, ovat Linux-ytimen toiminto, joka ryhmittelee prosesseja ja rajaa niiden resursseja. systemd luo jokaiselle palvelulle automaattisesti oman cgroupin, ja kaikki palvelun prosessit, FPM-palvelussa master ja kaikki workerit, kuuluvat siihen.

systemd:n muistivalinnat asettavat cgroup v2 -muistiohjaimen arvoja. Käyttääkö järjestelmänne cgroup v2:ta, näyttää lyhyt komento:

# "cgroup2fs" tarkoittaa: cgroup v2 on aktiivinen
stat -fc %T /sys/fs/cgroup/

Yleisten jakeluiden nykyiset versiot käyttävät oletuksena cgroup v2:ta. Jos komento näyttää jotain muuta, selvittäkää se ennen muutosta, sillä systemd-dokumentaatio kuvaa vanhat v1-valinnat vanhentuneiksi.

systemd MemoryMax PHP-FPM:lle

Raja tulee täydennystiedostoon, niin sanottuun drop-iniin. Näin paketin unit-tiedosto pysyy muuttumattomana, eikä päivitys ylikirjoita asetustanne. Palvelunimet vaihtelevat jakelun mukaan; esimerkki käyttää Debiania ja Ubuntua PHP 8.3:lla.

# Avaa muokkaimen drop-iniä varten: sudo systemctl edit php8.3-fpm
# Tulos: /etc/systemd/system/php8.3-fpm.service.d/override.conf
[Service]
MemoryAccounting=yes
MemoryHigh=1536M
MemoryMax=2G
MemorySwapMax=0
OOMPolicy=continue

Valinnat yleiskuvana systemd:n dokumentaation mukaan:

Valinta Vaikutus Huomio PHP-FPM:lle
MemoryHigh= kuristusraja: prosesseja hidastetaan voimakkaasti, muistia otetaan takaisin varsinainen ohjausarvo, hieman MemoryMax-arvon alapuolella
MemoryMax= ehdoton raja: jos sitä ei voi pitää, OOM-killer aktivoituu palvelussa viimeinen puolustuslinja
MemorySwapMax= vaihtoalueen raja 0 estää palvelun väistämästä sen sijaan swapiin
OOMPolicy= käyttäytyminen OOM-tapahtuman jälkeen: continue, stop tai kill continue antaa muiden workerien jatkaa
LimitAS=, LimitDATA= rlimitit prosessia kohden php.net:n mukaan tähän toimenpiteeseen sopimattomia

OOMPolicy= ansaitsee erityishuomion. Arvoilla stop tai kill systemd lopettaa OOM-tapahtuman jälkeen koko palvelun, ja kaikki tämän FPM-palvelun verkkosivustot kaatuvat. Mikä arvo on voimassa ilman omaa määritystä, määrää järjestelmän oletus DefaultOOMPolicy=. Tarkistakaa voimassa oleva arvo siksi sen sijaan, että olettaisitte sen.

Sen jälkeen käynnistäkää palvelu uudelleen ja lukitkaa poolissa memory_limit:

sudo systemctl restart php8.3-fpm
systemctl show php8.3-fpm -p MemoryHigh -p MemoryMax -p MemorySwapMax -p OOMPolicy -p MemoryCurrent
; Pool-tiedosto, esim. /etc/php/8.3/fpm/pool.d/seite1.conf
php_admin_value[memory_limit] = -1

Miten lukitseminen toimii, kuvaa php_admin_value: asetusten lukitseminen ylikirjoitukselta.

Oikean arvon löytäminen

Liian niukka raja aiheuttaa keskeytyksiä normaalikäytössä, liian antelias ei suojaa juuri lainkaan. Lähtökohta on todellinen kulutus.

  1. Tarkkailkaa kaikkien FPM-prosessien nykyistä muistintarvetta muutaman päivän ajan, myös kuormituspiikkien aikana.
  2. Selvittäkää workerin tyypillinen tarve ja kertokaa se palvelun kaikkien poolien arvolla pm.max_children.
  3. Varatkaa lisä master-prosessille, OPcachelle ja piikeille.
  4. Asettakaa MemoryHigh= hieman mitatun huippuarvon yläpuolelle, MemoryMax= vähän sen yläpuolelle.
  5. Hienosäätäkää arvot viikon kuluttua mittausten ja OOM-tapahtumien perusteella.
# Kaikkien PHP-FPM-prosessien muisti megatavuina (Resident Set Size)
ps -C php-fpm8.3 -o rss= | awk '{s+=$1} END {printf "%.0f MB\n", s/1024}'
# Kaikkien cgroupien jatkuva yleiskatsaus muistin mukaan lajiteltuna
systemd-cgtop -m

memory_limit-arvo kannattaa kirjata muistiin ennen muutosta. Päivityksen jälkeen voitte palata kiinteään rajaan PHP:ssä; miten sen valitsette järkevästi, kerrotaan kohdassa memory_limit-asetuksen määrittäminen oikein.

Valvonta: milloin raja laukeaa

Muistiraja, jonka laukeamista kukaan ei huomaa, on hiljainen virhelähde. Ydin laskee OOM-tapahtumat cgroupia kohden, ja systemd sekä ytimen loki kirjaavat ne.

# Palvelun cgroupin OOM-laskuri (polku cgroup v2:lla)
cat /sys/fs/cgroup/system.slice/php8.3-fpm.service/memory.events
# OOM-killerin ilmoitukset ytimen lokissa
journalctl -k --since "-1 day" | grep -i "out of memory"

Jos laskuri oom_kill kasvaa, ydin lopettaa workereita. Se voi viitata liian niukkaan rajaan, virheelliseen sovellukseen tai epätavallisiin pyyntöihin. Nykytilanteessa tällaiset tapahtumat kannattaa verrata samanaikaisiin käyttölokeihin.

Tärkeää

Muistiraja on siirtymäajan toimenpide eikä korvaa PHP-päivitystä. Suunnitelkaa päivitys rinnakkain ja käynnistäkää sen jälkeen kaikki PHP-prosessit uudelleen. Kaikki siirtymäajan toimenpiteet yleiskatsauksena ovat kohdassa Pikatoimet, jos ette voi vielä päivittää.

Mitä PHP:n sisällä tapahtuu muistin loppuessa, selittää memory_limit ja Out-of-Memory. Apachella mod_php:n kanssa sama tekniikka pätee Apache-palveluun, mutta PHP jakaa tällöin rajan itse verkkopalvelimen kanssa.

Yhteenveto: älkää unohtako toista puolikasta

memory_limit=-1 on puolustettava siirtymäajan toimenpide vain yhdessä ulkoisen rajan kanssa. systemd mahdollistaa tämän rajan muutamalla rivillä: MemoryHigh jarruna, MemoryMax kovana ylärajana, MemorySwapMax swapiin väistämistä vastaan ja tietoisesti valittu OOMPolicy. Mitatkaa ennen, tarkkailkaa jälkeen ja pitäkää mielessä, että palvelun kaikki poolit jakavat rajan.

Ennen kaikkea: suunnitelkaa toimenpiteen loppu. Päivityksen jälkeen korjattuun PHP-versioon palaatte kiinteään memory_limit-arvoon. Cgroup-rajan voitte säilyttää lisävarmistuksena karkaavia prosesseja vastaan.

Miten PHP SOS auttaa konkreettisesti?

  • Mittaus: Kartoitamme PHP-palveluidenne muistintarpeen ja johdamme siitä sopivat rajat.
  • Toteutus: Asetamme drop-init arvoilla MemoryHigh, MemoryMax ja sopiva OOMPolicy, lukitsemme memory_limit-arvon poolissa ja käynnistämme palvelut hallitusti uudelleen.
  • Valvonta: Liitämme OOM-tapahtumat valvontaan ja hienosäädämme arvoja.
  • Päivitykseen ohjaus: PHP-päivityspalvelun kautta asennamme päivityksen ja purkamme siirtymäarvot sen jälkeen järjestelmällisesti.

Usein kysytyt kysymykset

Miksi memory_limit pitäisi asettaa arvoon -1? Eikö se ole vaarallista?

Yksinään se olisi. php.net suosittelee memory_limit=-1-asetusta siirtymäajan toimenpiteenä nimenomaisesti yhdessä kontin tai cgroupin kautta asetetun muistirajan kanssa. Rajaus siirtyy siten PHP:stä käyttöjärjestelmään. Ilman tätä ulkoista rajaa arvoa ei pitäisi asettaa.

Voinko käyttää sen sijaan ulimitiä tai LimitAS=-asetusta?

Et. php.net neuvoo nimenomaisesti olemaan käyttämättä RLIMIT_AS- ja RLIMIT_DATA-rajoja, ja juuri nämä rajat asettavat ulimit -v, LimitAS= ja LimitDATA=. systemd-dokumentaatio huomauttaa lisäksi, että LimitAS= rajaa osoiteavaruutta, ei muistinkäyttöä.

Mitä tapahtuu, kun PHP-FPM saavuttaa rajan?

MemoryHigh-asetuksella ydin hidastaa prosesseja ja ottaa muistia takaisin. MemoryMax-asetuksella OOM-killer aktivoituu palvelun sisällä ja lopettaa prosessin, yleensä suuren workerin. Pysähtyykö sen jälkeen koko palvelu, määrää OOMPolicy=.

Koskeeko raja jokaista poolia erikseen?

Ei. Kaikki FPM-palvelun poolit jakavat yhden cgroupin ja siten yhteisen rajan. Sivustokohtaiset erilliset rajat vaativat erilliset FPM-palvelut tai kontit.

Miten asetan rajan Dockerissa tai Kubernetesissa?

Kontin muistirajoituksen kautta, esimerkiksi valinnalla --memory komennossa docker run tai resources.limits.memory Kubernetesissa. Molemmat käyttävät myös cgroupeja. Periaate pysyy samana: raja PHP:n ulkopuolella, memory_limit lukittuna PHP:ssä.

Lähteet

Pikayhteys

Jokainen tunti on tärkeä. Kirjoittakaa meille nyt.

Kuvatkaa lyhyesti järjestelmä, PHP-versio ja havainnot. Otamme yhteyttä, selvitämme tilanteen ja työskentelemme etänä, kunnes tapaus on käsitelty loppuun ja dokumentoitu.

Pikaapu