Накратко: За преходната мярка memory_limit=-1 при PHP2Shell PHP-FPM се нуждае от лимит на паметта извън PHP. Под systemd го задавате с MemoryHigh= като спирачка и MemoryMax= като твърда горна граница в допълващ файл за услугата FPM. ulimit, LimitAS= или LimitDATA= според php.net са неподходящи за това. Проверете освен това OOMPolicy=, за да не прекрати един отделен worker цялата услуга.
Препоръка, която първоначално учудва
Сред преходните мерки, които php.net публикува на 21 октомври 2026 г. за PHP2Shell, има една, която накара много администратори да се спрат: memory_limit=-1, тоест никакъв лимит на паметта в самия PHP. С години обратното се смяташе за добра практика.
Връзка с описанието на уязвимостта е очевидна. При CVE-2026-109618 състояние на недостиг на памет (Out-of-Memory) може да прекъсне PHP, докато данните на заявката са само частично инициализирани. Преходната мярка премества лимита на паметта извън PHP в операционната система. php.net я свързва изрично с две условия: стойността трябва да е заключена срещу презаписване, а лимитът трябва да идва чрез контейнер или cgroup, не чрез RLIMIT_AS или RLIMIT_DATA.
Който приложи само първата половина, има сървър без никакъв лимит на паметта. Тази статия показва втората половина. И заедно мярката остава преход: уязвимостта е отстранена едва с PHP 8.5.12, 8.4.27, 8.3.36, 8.2.35 или потвърден backport.
cgroups, обяснени в две изречения
Control Groups, накратко cgroups, са функция на ядрото на Linux, която групира процеси и ограничава ресурсите им. systemd създава автоматично за всяка услуга собствена cgroup и всички процеси на услугата, при услугата FPM тоест главният процес и всички worker процеси, принадлежат към нея.
Опциите за паметта на systemd задават стойности на контролера на паметта на cgroup v2. Дали Вашата система ползва cgroup v2, показва кратка команда:
# "cgroup2fs" означава: cgroup v2 е активна
stat -fc %T /sys/fs/cgroup/
Актуалните версии на разпространените дистрибуции ползват cgroup v2 по подразбиране. Ако командата покаже нещо друго, изяснете това преди прехода, защото документацията на systemd описва старите опции за v1 като остарели.
Задаване на systemd MemoryMax за PHP-FPM
Лимитът се записва в допълващ файл, т.нар. drop-in. Така файлът на unit от пакета остава непроменен, а актуализация не презаписва Вашата настройка. Имената на услугите се различават според дистрибуцията; примерът ползва Debian и Ubuntu с PHP 8.3.
# Отваря редактор за drop-in: sudo systemctl edit php8.3-fpm
# Резултат: /etc/systemd/system/php8.3-fpm.service.d/override.conf
[Service]
MemoryAccounting=yes
MemoryHigh=1536M
MemoryMax=2G
MemorySwapMax=0
OOMPolicy=continue
Опциите накратко, по документацията на systemd:
| Опция | Ефект | Указание за PHP-FPM |
|---|---|---|
MemoryHigh= |
граница на задушаване: процесите се забавят силно, паметта се възвръща | същинската управляваща стойност, малко под MemoryMax |
MemoryMax= |
абсолютна граница: ако не може да се удържи, OOM killer се активира в услугата | последна линия на защита |
MemorySwapMax= |
граница за разменяне | 0 предотвратява услугата да се измъкне в swap |
OOMPolicy= |
поведение след OOM събитие: continue, stop или kill |
continue оставя останалите worker процеси да работят |
LimitAS=, LimitDATA= |
rlimits за процес | според php.net неподходящи за тази мярка |
OOMPolicy= заслужава особено внимание. При stop или kill systemd след OOM събитие прекратява цялата услуга и всички сайтове на тази услуга FPM отпадат. Коя стойност важи без собствено указание, определя системната настройка по подразбиране DefaultOOMPolicy=. Затова проверете действащата стойност, вместо да я предполагате.
След това рестартирайте услугата и заключете memory_limit в пула:
sudo systemctl restart php8.3-fpm
systemctl show php8.3-fpm -p MemoryHigh -p MemoryMax -p MemorySwapMax -p OOMPolicy -p MemoryCurrent
; Файл на пул, напр. /etc/php/8.3/fpm/pool.d/seite1.conf
php_admin_value[memory_limit] = -1
Как работи заключването, описва php_admin_value: заключване на настройки срещу презаписване.
Намиране на правилната стойност
Твърде тесен лимит създава прекъсвания в нормална работа, твърде щедър почти не защитава. Изходната точка е действителното потребление.
- Наблюдавайте текущата нужда от памет на всички процеси на FPM в продължение на няколко дни, и при пикове на натоварването.
- Установете типичната нужда на един worker и я умножете по
pm.max_childrenна всички пулове на услугата. - Планирайте надбавка за главния процес, OPcache и пикове.
- Задайте
MemoryHigh=малко над измерения пик,MemoryMax=с известно разстояние над него. - След седмица коригирайте стойностите според измерванията и OOM събитията.
# Памет на всички процеси на PHP-FPM в MB (Resident Set Size)
ps -C php-fpm8.3 -o rss= | awk '{s+=$1} END {printf "%.0f MB\n", s/1024}'
# Текущ преглед на всички cgroups, подредени по памет
systemd-cgtop -m
Стойността на memory_limit преди прехода трябва да запишете. След актуализацията можете да се върнете към фиксиран лимит в PHP; как да го изберете смислено, е описано в Правилна настройка на memory_limit.
Наблюдение: кога лимитът се задейства
Лимит на паметта, чието задействане никой не забелязва, е тих източник на грешки. Ядрото брои OOM събитията за всяка cgroup, а systemd и протоколът на ядрото ги отбелязват.
# OOM брояч на cgroup на услугата (път при cgroup v2)
cat /sys/fs/cgroup/system.slice/php8.3-fpm.service/memory.events
# Съобщения на OOM killer в протокола на ядрото
journalctl -k --since "-1 day" | grep -i "out of memory"
Ако броячът oom_kill нараства, ядрото прекратява worker процеси. Това може да сочи твърде тесен лимит, грешно приложение или необичайни заявки. В сегашното положение си струва такива събития да се съпоставят с протоколите за достъп в същото време.
Лимитът на паметта е преходна мярка и не замества актуализацията на PHP. Планирайте актуализацията успоредно и след нея рестартирайте всички PHP процеси. Всички преходни мерки накратко са в Незабавни мерки, когато още не можете да пачнете.
Какво се случва вътре в PHP при недостиг на памет, обяснява memory_limit и Out-of-Memory. При Apache с mod_php същата техника важи за услугата Apache, но тогава PHP споделя лимита със самия уеб сървър.
Извод: не забравяйте втората половина
memory_limit=-1 е приемлива преходна мярка само заедно с външен лимит. systemd го прави възможен с няколко реда: MemoryHigh като спирачка, MemoryMax като твърда горна граница, MemorySwapMax срещу измъкване в swap и съзнателно избрана OOMPolicy. Измерете преди това, наблюдавайте след това и имайте предвид, че всички пулове на една услуга споделят лимита.
Преди всичко обаче: планирайте края на мярката. След актуализацията до коригирана версия на PHP се връщате към фиксиран memory_limit. Лимита на cgroup можете да запазите като допълнителна защита срещу излизащи извън контрол процеси.
Как помага PHP SOS конкретно?
- Измерване: отчитаме нуждата от памет на Вашите PHP услуги и извеждаме от нея подходящи лимити.
- Прилагане: задаваме drop-in файлове с
MemoryHigh,MemoryMaxи подходящаOOMPolicy, заключвамеmemory_limitв пула и рестартираме услугите контролирано. - Наблюдение: включваме OOM събитията в мониторинга и коригираме стойностите.
- Към пача: чрез PHP пач услугата инсталираме актуализацията и след това връщаме преходните стойности по ред.
Често задавани въпроси
Защо memory_limit трябва да е -1? Не е ли опасно?
Само по себе си би било. php.net препоръчва memory_limit=-1 като преходна мярка изрично заедно с лимит на паметта чрез контейнер или cgroup. Така ограничението се премества от PHP в операционната система. Без този външен лимит не бива да задавате стойността.
Мога ли вместо това да използвам ulimit или LimitAS=?
Не. php.net изрично съветва против RLIMIT_AS и RLIMIT_DATA, а точно тези лимити задават ulimit -v, LimitAS= и LimitDATA=. Документацията на systemd освен това сочи, че LimitAS= ограничава адресното пространство, не потреблението на памет.
Какво се случва, когато PHP-FPM достигне лимита?
При MemoryHigh ядрото забавя процесите и възвръща памет. При MemoryMax в рамките на услугата се активира OOM killer и прекратява един процес, обикновено голям worker. Дали след това спира цялата услуга, определя OOMPolicy=.
Важи ли лимитът за всеки пул поотделно?
Не. Всички пулове на една услуга FPM споделят една cgroup и следователно общ лимит. Отделни лимити за всеки сайт изискват отделни услуги FPM или контейнери.
Как да задам лимита в Docker или Kubernetes?
Чрез ограничението на паметта на контейнера, например опцията --memory при docker run или resources.limits.memory в Kubernetes. И двете също използват cgroups. Принципът остава същият: лимит извън PHP, memory_limit в PHP заключен.