Krótko: Dla środka przejściowego PHP2Shell memory_limit=-1 PHP-FPM potrzebuje limitu pamięci poza PHP. W systemd ustawiają go Państwo za pomocą MemoryHigh= jako hamulca i MemoryMax= jako twardej górnej granicy w pliku uzupełniającym dla usługi FPM. ulimit, LimitAS= lub LimitDATA= są według php.net do tego nieodpowiednie. Proszę ponadto sprawdzić OOMPolicy=, aby pojedynczy proces roboczy nie zakończył całej usługi.
Zalecenie, które początkowo dziwi
Wśród środków przejściowych, które php.net opublikował 21 października 2026 w sprawie PHP2Shell, jest jeden, który zaskoczył wielu administratorów: memory_limit=-1, czyli brak limitu pamięci w samym PHP. Przez lata za dobrą praktykę uchodziło coś odwrotnego.
Związek z opisem luki nasuwa się sam. W CVE-2026-109618 stan braku pamięci (Out-of-Memory) może przerwać PHP w chwili, gdy dane żądania są zainicjowane tylko częściowo. Środek przejściowy przenosi limit pamięci z PHP do systemu operacyjnego. php.net wiąże go wyraźnie z dwoma warunkami: wartość musi być zablokowana przed nadpisaniem, a limit musi pochodzić z kontenera lub cgroup, a nie z RLIMIT_AS czy RLIMIT_DATA.
Kto wdroży tylko pierwszą połowę, ma serwer bez jakiegokolwiek limitu pamięci. Ten artykuł pokazuje drugą połowę. Także razem środek pozostaje rozwiązaniem przejściowym: luka jest usunięta dopiero z PHP 8.5.12, 8.4.27, 8.3.36, 8.2.35 lub potwierdzonym backportem.
cgroups w dwóch zdaniach
Control Groups, w skrócie cgroups, to funkcja jądra Linuksa, która grupuje procesy i ogranicza ich zasoby. systemd zakłada dla każdej usługi automatycznie własną cgroup i należą do niej wszystkie procesy usługi, w przypadku usługi FPM proces główny i wszystkie procesy robocze.
Opcje pamięci w systemd ustawiają wartości kontrolera pamięci cgroup v2. To, czy Państwa system używa cgroup v2, pokazuje krótkie polecenie:
# "cgroup2fs" oznacza: cgroup v2 jest aktywna
stat -fc %T /sys/fs/cgroup/
Aktualne wersje popularnych dystrybucji domyślnie używają cgroup v2. Jeśli polecenie pokazuje coś innego, proszę to wyjaśnić przed przestawieniem, ponieważ dokumentacja systemd opisuje stare opcje v1 jako przestarzałe.
Ustawianie systemd MemoryMax dla PHP-FPM
Limit trafia do pliku uzupełniającego, tak zwanego drop-in. Dzięki temu plik jednostki z pakietu pozostaje niezmieniony, a aktualizacja nie nadpisze Państwa ustawienia. Nazwy usług różnią się zależnie od dystrybucji; przykład używa Debiana i Ubuntu z PHP 8.3.
# Otwiera edytor dla drop-in: sudo systemctl edit php8.3-fpm
# Wynik: /etc/systemd/system/php8.3-fpm.service.d/override.conf
[Service]
MemoryAccounting=yes
MemoryHigh=1536M
MemoryMax=2G
MemorySwapMax=0
OOMPolicy=continue
Opcje w skrócie, według dokumentacji systemd:
| Opcja | Działanie | Uwaga dla PHP-FPM |
|---|---|---|
MemoryHigh= |
granica dławienia: procesy są mocno hamowane, pamięć jest odzyskiwana | właściwa wielkość sterująca, nieco poniżej MemoryMax |
MemoryMax= |
granica bezwzględna: jeśli nie da się jej utrzymać, w usłudze uaktywnia się OOM-killer | ostatnia linia obrony |
MemorySwapMax= |
limit przenoszenia na dysk | 0 zapobiega ucieczce usługi do swapu |
OOMPolicy= |
zachowanie po zdarzeniu OOM: continue, stop lub kill |
continue pozwala pozostałym procesom roboczym działać dalej |
LimitAS=, LimitDATA= |
rlimity na proces | według php.net nieodpowiednie dla tego środka |
OOMPolicy= zasługuje na szczególną uwagę. Przy stop lub kill systemd po zdarzeniu OOM kończy całą usługę i wszystkie strony tej usługi FPM przestają działać. To, jaka wartość obowiązuje bez własnego wpisu, określa domyślne ustawienie systemu DefaultOOMPolicy=. Dlatego proszę sprawdzić obowiązującą wartość, zamiast ją zakładać.
Następnie zrestartować usługę i zablokować memory_limit w puli:
sudo systemctl restart php8.3-fpm
systemctl show php8.3-fpm -p MemoryHigh -p MemoryMax -p MemorySwapMax -p OOMPolicy -p MemoryCurrent
; Plik puli, np. /etc/php/8.3/fpm/pool.d/seite1.conf
php_admin_value[memory_limit] = -1
Jak działa blokowanie, opisuje artykuł php_admin_value: blokowanie ustawień przed nadpisaniem.
Znalezienie właściwej wartości
Zbyt ciasny limit powoduje przerwania w normalnej pracy, zbyt hojny prawie nie chroni. Punktem wyjścia jest rzeczywiste zużycie.
- Obserwować bieżące zapotrzebowanie na pamięć wszystkich procesów FPM przez kilka dni, także w szczytach obciążenia.
- Ustalić typowe zapotrzebowanie jednego procesu roboczego i pomnożyć przez
pm.max_childrenwszystkich pul usługi. - Uwzględnić zapas na proces główny, OPcache i szczyty.
MemoryHigh=ustawić tuż powyżej zmierzonej wartości szczytowej,MemoryMax=z pewnym odstępem powyżej.- Po tygodniu skorygować wartości na podstawie pomiarów i zdarzeń OOM.
# Pamięć wszystkich procesów PHP-FPM w MB (Resident Set Size)
ps -C php-fpm8.3 -o rss= | awk '{s+=$1} END {printf "%.0f MB\n", s/1024}'
# Bieżący przegląd wszystkich cgroups posortowany według pamięci
systemd-cgtop -m
Wartość memory_limit sprzed przestawienia warto zanotować. Po aktualizacji można wrócić do stałego limitu w PHP; jak go sensownie dobrać, opisuje artykuł Prawidłowe ustawienie memory_limit.
Monitorowanie: kiedy limit zadziała
Limit pamięci, którego zadziałania nikt nie zauważa, jest cichym źródłem błędów. Jądro zlicza zdarzenia OOM dla każdej cgroup, a systemd i dziennik jądra je odnotowują.
# Licznik OOM cgroup usługi (ścieżka przy cgroup v2)
cat /sys/fs/cgroup/system.slice/php8.3-fpm.service/memory.events
# Komunikaty OOM-killera w dzienniku jądra
journalctl -k --since "-1 day" | grep -i "out of memory"
Jeśli licznik oom_kill rośnie, jądro kończy procesy robocze. Może to wskazywać na zbyt ciasny limit, na wadliwą aplikację albo na nietypowe żądania. W obecnej sytuacji warto porównywać takie zdarzenia z logami dostępu z tego samego czasu.
Limit pamięci jest środkiem przejściowym i nie zastępuje aktualizacji PHP. Proszę planować aktualizację równolegle, a po niej zrestartować wszystkie procesy PHP. Przegląd wszystkich środków przejściowych znajduje się na stronie Środki doraźne, gdy nie mogą Państwo jeszcze załatać.
Co dzieje się wewnątrz PHP przy braku pamięci, wyjaśnia artykuł memory_limit i Out-of-Memory. W Apache z mod_php ta sama technika dotyczy usługi Apache, przy czym PHP dzieli wtedy limit z samym serwerem WWW.
Wniosek: nie zapomnieć o drugiej połowie
memory_limit=-1 jest uzasadnionym środkiem przejściowym tylko razem z limitem zewnętrznym. systemd umożliwia ten limit kilkoma wierszami: MemoryHigh jako hamulec, MemoryMax jako twarda górna granica, MemorySwapMax przeciw ucieczce do swapu i świadomie wybrane OOMPolicy. Proszę mierzyć wcześniej, obserwować potem i pamiętać, że wszystkie pule jednej usługi dzielą limit.
Przede wszystkim jednak: proszę planować koniec środka. Po aktualizacji do naprawionej wersji PHP wracają Państwo do stałego memory_limit. Limit cgroup mogą Państwo zachować jako dodatkowe zabezpieczenie przed wymykającymi się spod kontroli procesami.
Jak konkretnie pomaga PHP SOS?
- Pomiar: ustalamy zapotrzebowanie na pamięć Państwa usług PHP i wyprowadzamy z niego odpowiednie limity.
- Wdrożenie: ustawiamy drop-iny z
MemoryHigh,MemoryMaxi odpowiednimOOMPolicy, blokujemymemory_limitw puli i kontrolowanie restartujemy usługi. - Obserwacja: zdarzenia OOM włączamy do monitoringu i korygujemy wartości.
- Prowadzenie do poprawki: za pośrednictwem usługi poprawek PHP wgrywamy aktualizację i potem w uporządkowany sposób wycofujemy wartości przejściowe.
Częste pytania
Dlaczego memory_limit ma być ustawiony na -1? Czy to nie jest niebezpieczne?
Samo w sobie byłoby. php.net zaleca memory_limit=-1 jako środek przejściowy wyraźnie razem z limitem pamięci przez kontener lub cgroup. Ograniczenie przenosi się w ten sposób z PHP do systemu operacyjnego. Bez tego zewnętrznego limitu nie należy ustawiać tej wartości.
Czy mogę zamiast tego użyć ulimit lub LimitAS=?
Nie. php.net wyraźnie odradza RLIMIT_AS i RLIMIT_DATA, a właśnie te limity ustawiają ulimit -v, LimitAS= i LimitDATA=. Dokumentacja systemd zwraca ponadto uwagę, że LimitAS= ogranicza przestrzeń adresową, a nie zużycie pamięci.
Co się dzieje, gdy PHP-FPM osiąga limit?
Przy MemoryHigh jądro hamuje procesy i odzyskuje pamięć. Przy MemoryMax w obrębie usługi uaktywnia się OOM-killer i kończy jeden proces, zwykle duży proces roboczy. To, czy potem zatrzyma się cała usługa, reguluje OOMPolicy=.
Czy limit obowiązuje dla każdej puli osobno?
Nie. Wszystkie pule jednej usługi FPM dzielą jedną cgroup, a więc wspólny limit. Osobne limity dla każdej strony wymagają osobnych usług FPM lub kontenerów.
Jak ustawić limit w Docker lub Kubernetes?
Przez ograniczenie pamięci kontenera, na przykład opcję --memory w docker run lub resources.limits.memory w Kubernetes. Oba również korzystają z cgroups. Zasada pozostaje ta sama: limit poza PHP, memory_limit w PHP zablokowany.