Это четвёртая статья серии «Эксплуатация VPS / DevOps-база». В первой мы поставили сервер на ноги, во второй поняли, как вообще что-то на нём живёт, в третьей подняли nginx и завели HTTPS. Но вы же помните: nginx сам WordPress не исполняет — он передаёт запрос «наверх», через unix-сокет. Мы оставили за этим сокетом голый PHP-FPM по умолчанию, и обещали, что именно там рождаются «белый экран при десятке параллельных гостей» и 502 Bad Gateway. Пришло время залезть внутрь.
PHP-FPM — это и есть тот «движок», на котором крутится весь ваш WordPress: каждый запрос к странице, к админке, к AJAX-плагину — это работа процесса PHP-FPM. По умолчанию он настроен «на среднюю машину», а у вас на носу дешёвый VPS с гигабайтом-двумя памяти. Разница между «настроено по умолчанию» и «посчитано под вашу память» — это между сайтом, который стоит при всплеске трафика, и сайтом, который его переживает. В этой статье мы: разберём, что такое пул процессов и зачем он вообще nginx; научимся считать число процессов по свободной памяти (а не гадать); разберёмся, что живёт в php.ini, а что в www.conf и чем php_admin_value отличается от php_value; включим Opcache (самая большая бесплатная прибавка скорости WordPress) и научимся его «прогревать»; и наконец научимся читать slowlog — лог медленных запросов, который за минуту называет того, кто тормозит страницу, вместо гаданий «кто же виноват: тема, плагин или база».
Термины, которые введём один раз: PHP-FPM (FastCGI Process Manager, «менеджер процессов FastCGI») — программа, которая держит группу процессов PHP и отвечает на FastCGI-запросы от веб-сервера. Мастер-процесс (master) — главный, он сам не исполняет код, а управляет воркерами (читает конфиг, спавнит/убивает, следит). Воркер (worker, «рабочий») — процесс, который реально исполняет PHP-код запроса. Пул (pool) — именованный набор воркеров с общим конфигом (обычно один пул = один сайт). Байткод — скомпилированное представление PHP-файла, которое интерпретатор исполняет быстрее, чем исходный текст. Все домены и пути условные (
example.test,/var/www/example.test); подставляйте свои.
Часть I. Что такое пул процессов и зачем он nginx
Чтобы не воспринимать PHP-FPM как «магию, которая где-то там отвечает на сокет», разложим на физическую картину.
1. Как выглядит жизнь одного запроса к WordPress
Возьмём запрос браузера на https://example.test/2026/10/post/ и проведём его по цепочке:
- nginx принимает запрос по 443 (см. Часть IV про TLS), видит по
location ~ \.php$, что это динамика, и вызываетfastcgi_pass unix:/run/php/php8.2-fpm.sock;— то есть пишет «внутрь сокета». - мастер-процесс PHP-FPM этого сокета не исполняет запрос. Он следит за пулом; очередь воркеров у него в подчинении.
- Один свободный воркер берёт соединение, исполняет PHP-код (
index.php→ WordPress → тема Kadence → запрос к базе), и возвращает nginx’у готовый HTML по тому же FastCGI-протоколу. - nginx отдаёт HTML браузеру, а воркер освобождается и ждёт следующего запроса.
Ключевое: воркеров конечное число, и они заняты не весь день, а только пока исполняют запрос. Если все воркеры заняты (много гостей / тормозящий плагин), новый запрос стоит в очереди; если очередь переполнена — nginx получит отказ на сокет и вернёт 502 Bad Gateway. Именно поэтому «белый экран / 502 при наплыве» — это почти всегда про число и скорость воркеров, а не про nginx.
2. Почему PHP-FPM, а не другой способ подключить PHP
PHP можно исполнять по-разному. Для самописного VPS под WordPress реалистично сравнивать два:
- mod_php (внутри Apache). PHP вшит в веб-сервер; каждый Apache-поток тащит интерпретатор с собой. Это тяжёло и «вязаное» — не разорёшь, не перезапустишь независимо. (nginx так не умеет, у него только FastCGI-путь.)
- PHP-FPM (отдельная программа). PHP-исполнение вынесено в свой демон, общающийся с веб-сервером по сокету. nginx остаётся лёгким «фасадом», а PHP-процессы живут своей жизнью: их можно ограничивать по памяти, перегруппировывать по сайтам, перезапускать отдельно, не роняя фронтенд. Это то, что и ставили в Части III.
Плюс PHP-FPM даёт то, чего у mod_php нет: пулы. Можно дать каждому сайту свой набор процессов, своего пользователя и свои лимиты, живя при этом на одном установленном PHP (см. Часть V про изоляцию нескольких сайтов). Для того кто держит на одной машине несколько доменов (у вас не один же сайт), это и есть главное удобство.
3. Где живут файлы и как это всё зовётся
# какой PHP и какой FPM вообще стоит
php -v
ls /etc/php/ # папки версиями: 8.1 8.2 8.3 …
# где конфиг пула и где php.ini для fpm-окружения
ls /etc/php/8.2/fpm/ # файлы этой версии
ls /etc/php/8.2/fpm/pool.d/ # пулы: www.conf и т.п. (один файл = один пул)
cat /etc/php/8.2/fpm/php-fpm.conf # главный, он включает pool.d/*.conf
# как называется сам юнит (по нему делаем restart/reload)
systemctl status php8.2-fpm --no-pager | head -5
Два файла, которые мы будем править:
| Файл | Область действия | Что задаём |
|---|---|---|
/etc/php/8.2/fpm/php.ini | весь PHP этого окружения (все пулы и CLI-версия с другой копией php.ini) | memory_limit, opcache.*, базовые upload_*/post_max_size, max_execution_time |
/etc/php/8.2/fpm/pool.d/www.conf | один пул ([www]) | режим pm, число детей, listen= (сокет), пользователь/группа, точечные php_admin_value |
⛔ Фатал, перенесённый из конвенций Ветки B (как и в Части IV про systemd): если пишете свой цикл-воркер на PHP (у вас похожий
ai-news-autopilot), никаких сетевых вызовов в первую строку до готовности процесса — только лениво, внутри рабочего цикла. PHP-FPM сам по себе ленив к старту, но кастомный «долгий» процесс должен уметь не умирать в первые секунды: иначе словите стартовыйexit-codeи рестарт-шторм в логах пула.
Часть II. Пул и его число: pm, pm.max_children и как считать память
Сердце настройки — секция [www] в www.conf. Разберём построчно и, главное, как выбрать числа для вашего VPS, а не «взять из статьи».
1. Режим pm — как пул набирает воркеров
Директива pm принимает три значения, и выбор определяет всё поведение пула под нагрузкой:
pm = static— пул держит ровноpm.max_childrenпроцессов всегда. Ни больше, ни меньше. Минимум сюрпризов и минимум задержек «проснуться». Лучший выбор, когда памяти мало и вы точно знаете бюджет (на 512 МБ–1 ГБstaticчестнееdynamic: dynamic сначала плодит детей по запросу, и в пик может перескочить через вашу реальную память).pm = dynamic(дефолт) — число детей плавает междуpm.min_spare_serversиpm.max_spare_servers, но не большеpm.max_children. Пул сам гасит простаивающих и будит нужных. Оправдан на машине с запасом RAM и неравномерным трафиком.pm = ondemand— процессы создаются только под запрос и умирают после простояpm.process_idle_timeout. Экономит память на сайтах, к которым почти никто не ходит (лендинг, внутренний админ-домен, второй сайт «для проформы»). Минус: первый запрос после тишины «холодный».
2. Директивы pm.* под динамический режим
; /etc/php/8.2/fpm/pool.d/www.conf (секция [www])
pm = dynamic
pm.max_children = 10 ; потолок одновременно живых воркеров
pm.start_servers = 3 ; сколько создать при старте пула
pm.min_spare_servers = 2 ; сколько всегда держать свободными (ниже — породит)
pm.max_spare_servers = 5 ; сколько свободных можно копить (выше — погасит)
pm.max_requests = 500 ; после скольких запросов воркер мягко пересоздаётся
Что делает каждая (важно не только число, но и смысл):
pm.max_children— главная цифра статьи. Максимум процессов, которые исполняют код одновременно. Все они держат память одновременно. Если у вас 10 детей и каждый ест ~60 МБ — это ~600 МБ только PHP-воркеров. Превысили физическую память — OOM-killer кого-то из них (или базу, или SSH!) прирежет.pm.start_servers— сколько воркеров поднять сразу при старте пула (чтобы не было «холодного» первого запроса после рестарта).pm.min_spare_servers/pm.max_spare_servers— нижняя и верхняя границы числа свободных (не занятых запросом) процессов. Пул старается, чтобы свободных было между этими числами: кончилось нижеmin— родит детей; стало большеmax— погасит лишних. (Приstaticони не нужны; приdynamicэто и есть регулятор.)pm.max_requests— после скольки обработанных запросов воркер аккуратно пересоздаётся (master убивает старый, поднимает новый). Это против утечек памяти в плагинах: воркер, подлизавший 500 запросов, не утащит накопленный мусор в бесконечность. Ставят500–1000; не0(без рестарта — при утечке пул разбухнет).
3. Как считать pm.max_children по памяти (формула, а не «у меня 40»)
Классическая ошибка — ставить max_children «на глаз» или копировать чужие цифры с VPS вдвое жирнее вашего. Считают так.
Шаг 1. Узнать, сколько памяти реально можно отдать PHP-FPM.
# свободная память на машине
free -h
# и сколько сейчас ест ВАС средний воркер:
ps -o rss= -C php-fpm8.2 --no-headers | awk '{s+=$1; n++} END {if(n) printf "воркеров: %d, средний RSS: %.1f МБ\n", n, s/n/1024}'
free -h покажет Available — сколько реально свободно с учётом кэшей. Из этого вычитаем запас на ОС, на базу (MariaDB живёт рядом, см. следующую статью №5), и swap как подстраховку (из Части V про фундамент). Не отдавайте PHP всю память — база и система задыхаются первыми, а OOM выбирает жертву по «вредности».
Шаг 2. Измерить память одного воркера под реальной нагрузкой. Не «в простое», а когда пул обслуживает живой WordPress-запрос. Значение RSS (resident set size — сколько процесс занимает физической памяти) из ps выше — это и есть ваш ориентир. Для типичного WordPress-воркера это обычно 40–80 МБ (без Opcache), с Opcache один воркер занимает меньше, потому что байткод разделяется.
Шаг 3. Делим.
pm.max_children ≈ (доступно_МБ − запас_МБ) / RSS_одного_воркера_МБ
Пример (числа условные, подставьте свои): доступ 1200 МБ, оставляем 400 МБ запасу/базе/ОС, один воркер ест 60 МБ → (1200−400)/60 ≈ 13. Берём с осторожностью вниз: pm.max_children = 12.
⚠️ Про Opcache и «память одного воркера». Opcache держит общий пул байткода в разделяемой памяти (одна копия на все воркеры, см. Часть IV) — это не на воркера, а на весь пул. Не складывайте
memory_consumptionOpcache в формулу «на воркер»: он съедает память из того же бюджета «доступно», поэтому формула становитсяpm.max_children ≈ (доступно − запас − opcache_memory_consumption − буфер_базы) / RSS_воркера. Проще: сначала включите Opcache и отберите ему память, потом считайте детей.
4. Проверка после правки
После изменения pm.* — перечитать конфиг пула без разрыва запросов (master перезагрузится и аккуратно сменит воркеров):
# синтаксис пула/конфига
sudo php-fpm8.2 -t
# мягко перезагрузить пул (не роняя соединения; systemd-терминология из Части III)
sudo systemctl reload php8.2-fpm
# посмотреть, сколько детей реально поднялось
ps -C php-fpm8.2 --no-headers | wc -l
Часть III. www.conf против php.ini: что и где задавать
Оба файла правят поведение PHP, но разными методами. Путаница порождает «я поставил лимит, а он не подействовал». Разберём слои.
1. Уровни настроек и приоритет
У PHP-настроек есть «скоуп» (где действует): PHP_INI_ALL (можно где угодно, хоть в рантайме), PHP_INI_SYSTEM (только в системном конфиге/пуле, не в рантайме), PHP_INI_PERDIR (в конфиге директории) и т.д. Для нас практически это значит:
php.ini— базовый слой. То, что задано здесь, действует для всех пулов этогоphp.ini. Это место для общих вещей: включение расширений,memory_limit, всеopcache.*, дефолтныеupload_max_filesize/post_max_size/max_execution_time, часовой пояс.- пул
[www]— слой поверх, точечный и на конкретного пользователя/сайт. Здесь задают сокет, владельца процесса, число детей, и умеют переопределять php.ini-настройки черезphp_admin_value/php_value/php_flag— см. ниже.
Приоритет на практике: пул > php.ini для значений, которые пул объявил через php_admin_* или php_* директивы; сами php.ini-файлы между cli и fpm — разные копии (у вас в /etc/php/8.2/cli/php.ini и /etc/php/8.2/fpm/php.ini разное окружение!).
2. php_admin_value vs php_value vs php_flag — и почему это путают
В пуле можно задавать PHP-параметры напрямую. Три формы, разница принципиальная:
; /etc/php/8.2/fpm/pool.d/www.conf
php_admin_value[upload_max_filesize] = 32M ; НЕ переопределяется ниже (ни кодом, ни ini-файлом в рантайме)
php_value[post_max_size] = 34M ; переопределяется ini_set()/.htaccess (мягкая)
php_flag[display_errors] = off ; логический on/off (как php_value, но для булевых)
php_admin_value[ключ]— значение вбивается наглухо: PHP не сможет его сменить изini_set()в плагине или из.htaccess/.user.iniв рантайме. Так ставят то, что админ должен контролировать (безопасность, лимиты,disable_functions). Если ваш плагин «сам себе ставит лимит» и вы хотите, чтобы ваше слово было финальным —php_admin_value.php_value[ключ]— мягкое: задаёт дефолт, но плагин может переопределить черезini_set()или пользовательский.user.ini. Ставят то, где рантайм-гибкость допустима.php_flag[ключ] = on|off— то же, чтоphp_value, но для булевых настроек (display_errors,engine); пишитеon/off, а не1/0.
3. Согласование лимитов загрузки: nginx (фронт), php.ini (ядро), пул (уточнение)
Из Части VI про nginx осталась ловушка: «лимитов два, а не один». Здесь закрываем окончательно. Чтобы загрузить файл размером X, он должен пройти три сита, и любое может его порезать:
- nginx —
client_max_body_size(фронт, Часть VI). Режет тело запроса, отдаёт413 Request Entity Too Large. - php.ini —
upload_max_filesize(макс одного загружаемого файла) иpost_max_size(макс всего POST-тела; должен быть ≥upload_max_filesize). Режет уже внутри PHP — это не «413», а молча пустой$_FILES/ «файл не загружен» / белый экран. - пул
[www]— те жеupload_max_filesize/post_max_sizeчерезphp_admin_value, если хотите разное для разных сайтов на одном PHP.
Корректная связка (числа условные, ставьте под себя, согласованно):
; /etc/php/8.2/fpm/php.ini — базовый дефолт
upload_max_filesize = 32M
post_max_size = 34M
memory_limit = 256M ; на воркера, под ваш пик запроса (см. ниже про память)
; /etc/php/8.2/fpm/pool.d/www.conf — если этому пулу нужно крупнее
php_admin_value[upload_max_filesize] = 64M
php_admin_value[post_max_size] = 66M
php_admin_value[memory_limit] = 384M
И в nginx-server: client_max_body_size 70m; (чуть больше POST, чтобы nginx не резал раньше PHP). Правило неравенств: client_max_body_size (nginx) > post_max_size (PHP) > upload_max_filesize (PHP).
Про memory_limit отдельно: это потолок памяти на один запрос в PHP, не на процесс. WordPress с тяжёлыми плагинами/редактором/экспортом может упереться в 128M (дефолт по умолчанию у части сборок) и упасть с «Allowed memory size exhausted» (это не 502, а белый экран / fatal в error.log). Поднимают до 256M–512M, но помните: memory_limit × pm.max_children ≈ реальный пик памяти пула; не ставьте 512M при max_children=12 — это уже 6 ГБ на машину, которой и так тесно.
4. www.conf как файл пула, а не только про PHP
Помимо php_*, в секции [www] живут и чисто-FPM настройки, которые нам пригодятся в Части V:
[www]
user = www-data ; от кого работают воркеры этого пула
group = www-data
listen = /run/php/php8.2-fpm.sock ; сокет этого пула (файл, права)
listen.owner = www-data ; владелец файла сокета (его читает nginx)
listen.group = www-data
listen.mode = 0660 ; права на сокет: читают лишь owner/group (безопасно)
listen.mode 0660 + listen.owner/group — это защита: до сокета допускаются только nginx (в группе www-data) и root. Не ставьте 0666 (все подряд смогут слать запросы вашему PHP).
Часть IV. Opcache: байткод в памяти и «прогрев» кэша
Это раздел с самым большим отношением «прирост скорости / затраченные усилия». Если на вашем PHP-FPM Opcache выключен или работает с дефолтами — включение и правильные параметры заметно снижают нагрузку на CPU и ускоряют отклик страницы, потому что WordPress больше не перекомпилирует одни и те же файлы на каждый запрос.
1. Что делает Opcache простыми словами
PHP-код — текст. Перед исполнением интерпретатор компилирует каждый .php в байткод. Без кэша это происходит на каждый запрос: WordPress тянет ядро, тему, десятки плагинов — их файлы компилируются заново каждый раз. Opcache (OPcode cache) хранит этот байткод в общем сегменте разделяемой памяти: первый запрос скомпилировал — дальше все воркеры берут готовый байткод. Компиляция — это чистый CPU, и Opcache именно его экономит.
Есть ещё var cache (кэш пользовательских переменных, apcu) — он кэширует данные, а не код; это отдельная тема, ниже лишь упомянем, что на WordPress его тоже полезно иметь (не смешивайте: Opcache — про байткод, APCu — про данные/сессии).
2. Включаем и задаём параметры
Всё в /etc/php/8.2/fpm/php.ini (внимание: секция [opcache] в php.ini обычно пишется как opcache.имя):
; /etc/php/8.2/fpm/php.ini
opcache.enable=1 ; сам переключатель (для FPM)
; opcache.enable_cli=0 ; для CLI не включаем, если не нужно (CLI-процесс одноразовый)
; сколько памяти выделить под байткод, МБ (ОДИН общий сегмент на все воркеры)
opcache.memory_consumption=192
; отдельный маленький буфер под строковые константы файлов (дефолт 16, для WP часто ставят 32–64)
opcache.interned_strings_buffer=32
; максимум кэшируемых файлов; если файлов больше — старые вытесняются раньше времени
opcache.max_accelerated_files=20000
; проверять изменённые файлы каждые N секунд вместо каждой проверки (0 = только руками после деплоя)
opcache.revalidate_freq=60
; в продакшене: НЕ проверять, не менялся ли файл (самый большой выигрыш CPU),
; сброс делаем ЯВНО после обновления. ⚠️ см. грабли ниже
opcache.validate_timestamps=1 ; новичку держим 1; 0 — только с дисциплиной сброса
; безопасно для sharedhosting с кучей путей
opcache.fast_shutdown=1
Подбор memory_consumption: WordPress с плагиной (ядро+тема+плагины) компилируется в байткод размером в десятки-сотни МБ. 64–128 МБ хватает на скромный сайт, 192–256 МБ — на средний. Как понять, что не хватает, — смотрим ниже счётчиками.
3. Грабли validate_timestamps и «почему сайт не обновился»
opcache.validate_timestamps=1 (безопасный дефолт): Opcache периодически смотрит, не трогали ли файл. Период задаёт opcache.revalidate_freq (секунды). При 1 и revalidate_freq=60 вы будете подхватывать изменения файлов до минуты позже — это нормально для продакшена, но не «мгновенно».
opcache.validate_timestamps=0 (агрессивный режим): Opcache вообще не смотрит на файлы — байткод считается вечно валидным. Это экономит ещё CPU (нет stat() по каждому файлу), но: обновили плагин/тему — а воркеры крутят СТАРЫЙ байткод, пока вы не сбросите кэш. Отсюда классическая ситуация «залил исправление, а сайт по-прежнему битый». При 0 обязан порядок после всякого изменения кода:
# сбросить Opcache без перезагрузки пула (нужен CLI доступ того же SAPI)
# вариант 1 — рестарт пула (грубо, но гарантированно):
sudo systemctl reload php8.2-fpm
# вариант 2 — точечно по API (если есть скрипт сброса):
sudo php -r 'opcache_reset();' # работает для CLI-SAPI; для FPM-SAPI нужен скрипт-хук
Практика для одного человека на self-hosted: держите
validate_timestamps=1сrevalidate_freq=60. Получили «изменения подхватываются с задержкой до минуты» и не словите «залил и не работает». В0переходят только когда замерите, что stat-вызовы реально жгут CPU на холодном старте, и приучите себя/CI сбрасывать кэш после деплоя (см. CI/CD своими словами).
4. Прогрев (warm-up) и как смотреть счётчики
Проблема cold start. После рестарта пула/перезагрузки Opcache пуст: первые запросы дорогие (компилируют файлы заново). На оживлённом сайте это «просадка отклика на первую сотню хитов». Прогрев (warm-up) — заранее «прогнать» основные URL/файлы, чтобы они скомпилировались до прихода реальных гостей.
# примитивный прогрев: обойти главную и пару тяжёлых страниц, игнорируя тело
curl -s -o /dev/null https://example.test/
curl -s -o /dev/null https://example.test/wp-login.php
curl -s -o /dev/null https://example.test/feed/
# если есть wp-cli (WP-CLI), есть и аккуратные обходы/грелки; встраивается в cron или
# в systemd-таймер (механика — см. Части II/III про systemd) сразу после reload пула
Оцифровать «хорошо ли живёт Opcache» можно только через счётчики. Проще всего — скрипт, выводящий opcache_get_status() (затем удалить/закрыть; статус-скрипт не должен быть публичным):
<?php
// /var/www/example.test/opcache-status.php — ВРЕМЕННЫЙ диагностический файл, закрыть
echo json_encode(opcache_get_status(false), JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
// Ключевые поля: opcache_enabled, memory_usage.used_memory, statistics.num_cached_scripts,
// statistics.missed_scripts (промахи — много = мало памяти/частые изменения),
// statistics.blacklist_miss_ratio.
Что смотреть в нём:
used_memoryблизко кmemory_consumption+num_cached_scriptsрастёт без падения — памяти хватает.missed_scriptsмного и растёт — байткод вытесняется: поднимитеmemory_consumption/max_accelerated_files.hash_updates_in_cycle/num_hitsиnum_misses—hits/missesпоказывает, как часто байткод перекомпилируется.
5. Про APCu и сессии одной строкой
Opcache кэширует код. APCu (apcu.enable=1) кэширует данные: транзакционные объекты, и главное — объект-кэш WordPress (wp-content/object-cache.php, WP_OBJECT_CACHE). На WordPress с APCu-бэкендом админ-запросы (которых не кэширует страница) заметно легче; сессии можно тоже вынести в APCu (session.save_handler). Это тема Кеширование из серии про архитектуру; здесь зафиксировали, что «Opcache есть» и «APCu тоже стоит включить» — но не смешивать: это два разных кэша.
Часть V. Изоляция нескольких сайтов на одном PHP-FPM пулами
У вас не один сайт, и хочется, чтобы один «затормозивший» не тянил за собой остальные. Именно это решают пулы — и это вторая по ценности часть статьи после pm.max_children.
1. Идея: один PHP, много пулов, разные сайты
php-fpm.conf включает pool.d/*.conf, и .conf-файл с секцией [имя] — это отдельный пул. Дефолтный — [www] в www.conf. Вы добавляете: example.test.conf с секцией [example], shop.test.conf с [shop] и т.д. Каждый пул:
- свой сокет (
listen = /run/php/php8.2-fpm-example.sock), - свой воркер-пользователь (
user = site_example), - свои
pm.*-числа иphp_admin_value(лимиты/память).
В nginx у соответствующего server этого домена ставите fastcgi_pass unix:/run/php/php8.2-fpm-example.sock; (см. Часть III) — и весь трафик домена пойдёт в его пул.
2. Что это даёт по факту
- Файрвол от соседей по нагрузке. Один сайт словил DDoS/тяжёлый cron — кончились его воркеры → у него
502, а второй сайт живёт в своём пуле со своими детьми. Без пулов (всё в один[www]) «жадный» сайт забивает всех. - Разные права и безопасность.
user = site_exampleиgroup = site_example+open_basedirв пуле → процессы этого сайта физически не читают файлы чужого сайта. Это серверная половина изоляции (вторую половину — пользователь WordPress и права файлов — см. Часть V про права иauthorized_keys). - Разные лимиты на запрос. У «медиа-сайта» крупный
upload_*иmemory_limit, у лендинга — мелкий:php_admin_valueв пуле позволяет не раздувать лимиты ради всех.
3. Шаблон пула и «кому нужен лишний пул»
; /etc/php/8.2/fpm/pool.d/example.test.conf
[example]
user = site_example
group = site_example
listen = /run/php/php8.2-fpm-example.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660
pm = ondemand ; сайт с малым трафиком — не держим детей зря (см. Части II)
pm.max_children = 5
pm.process_idle_timeout = 10s
pm.max_requests = 300
php_admin_value[upload_max_filesize] = 64M
php_admin_value[post_max_size] = 66M
php_admin_value[memory_limit] = 256M
php_admin_value[open_basedir] = /var/www/example.test:/tmp:/var/lib/php/sessions
php_admin_value[session.save_path] = /var/www/example.test/sessions ; своя папка сессий
; медленный лог и пороги — см. Часть VI
slowlog = /var/log/php-fpm/example-slow.log
request_slowlog_timeout = 5sПлюсы/минусы пулов честно: плюсы — изоляция нагрузки, раздельные права и лимиты, точечный slowlog на сайт. минусы — суммарное число детей по всем пулам не должно превышать память машины (суммируйте pm.max_children × RSS по каждому пулу в один бюджет); на совсем маленьком VPS для второго сайта проще ondemand, а не static-пул; на одном сайте пулы не нужны, достаточно одного [www]. Не раздувайте количество: изоляция нужна там, где есть конкуренция за ресурсы.
Часть VI. slowlog и error log: как узнать, кто тормозит, без гаданий
«Страница открылась за 4 секунды. Почему?» WordPress не скажет. PHP-FPM скажет — если включить. Разберём два разных лога, которые новички путают.
1. Две разные беды — два разных лога
| Лог | Файл (обычно) | Что ловит |
|---|---|---|
| PHP error log | /var/log/php8.2-fpm.log (или как в php.ini: error_log) | фатальные ошибки, warning, «memory exhausted», «Allowed memory size», фаталы плагинов. Отвечает на «почему упало». |
| slowlog пула | slowlog в секции [pool] (например /var/log/php-fpm/example-slow.log) | стек-трейс того, что выполняется дольше request_slowlog_timeout. Отвечает на «что именно тормозит». |
Про первый, error log, с точки зрения systemd-терминов — PHP-FPM пишет и туда, и в syslog/journal; читать можно и так:
# ошибки PHP-FPM через journal (юнит-лог; см. Части V про диагностику systemd)
sudo journalctl -u php8.2-fpm -p err --since today --no-pager
# или в файле
sudo tail -n 50 /var/log/php8.2-fpm.log
2. Включаем slowlog и «ловим» тормозящий запрос
slowlog не включён по умолчанию (в стандартных пулах его часто нет). Включается двумя директивами в пуле:
; /etc/php/8.2/fpm/pool.d/www.conf
slowlog = /var/log/php-fpm/$pool-slow.log ; $pool = имя пула; папка должна существовать и быть прав пула
request_slowlog_timeout = 5s ; дольше 5 сек воркер исполняет запрос → пишем трейс
; опционально: ловить не только «долго», но и «застряло» (по умолчанию выкл)
; request_terminate_timeout = 60s ; убить запрос, идущий дольше 60 сек (защита от зависших)
request_slowlog_timeout — ключевая директива. Заставляет FPM: если воркер на запросе дольше порога — сделать дамп трейса выполнения в slowlog, не прерывая запрос (он продолжится; мы просто зафиксируем «где он висел»). Это и есть ответ на «какой плагин/функция тормозит».
Как выглядит запись в slowlog:
[2026-10-17 20:01:12] [pool example] pid 8123
script_filename = /var/www/example.test/index.php
[0x...] wp_get_attachment_image() /var/www/example.test/wp-includes/media.php:712
[0x...] ... kadence_post_thumbnail() /var/www/example.test/wp-content/themes/kadence/inc/.../post-thumbnail.php:33
# ↑ вот эта последняя функция/файл сверху — это и есть «кто тормозит»,
# в отличие от error log, который скажет только «упало».
Вы видите цепочку вызовов (backtrace) от PHP-FPM-ядра вниз до конкретного файла WordPress/темы/плагина. Идёте «снизу вверх» — самая верхняя ваша строка (в wp-content/... или wp-includes/...) обычно и есть виновник: медленная функция плагина, WP_Query без ограничения, генерация миниатюры, внешний HTTP-запрос (transients/REST к стороннему API).
3. Как читать slowlog осмысленно
# показать последние 50 строк и группировать по верхним фреймам
sudo tail -n 50 /var/log/php-fpm/example-slow.log
# быстро увидеть, кто самый частый «тормоз» (верхний не-ядерный фрейм):
sudo grep -hoP '.*\.php:\d+' /var/log/php-fpm/example-slow.log | sort | uniq -c | sort -rn | head
Правило чтения: если фреймы повторяются из запроса в запрос в одном и том же файле плагина — это плагин, ищем у него кэш/оптимизацию или отключаем. Если тормозит wp-includes/... на тяжёлых WP_Query/get_posts — вероятна база: оптимизация запросов/индексов (см. Оптимизация баз данных), это тема следующей статьи про MariaDB.
4. Пороги и «не спамить»
request_slowlog_timeout = 5s— разумный старт: фиксирует явно-медленные запросы, не засоряя лог «нормальными по 1-2 сек». На плагинных «нормальных 1-3 сек» ставьте выше.request_terminate_timeout— уже убивает запрос дольше порога (по умолчанию не задан = ждём сколько PHPmax_execution_time). Нужен, если есть зависающие намертво запросы (внешние API, которые молчат), но он обрывает и «полезные длинные» операции (импорт, генерация больших миниатюр) — ставьте осознанно и выше реальных рабочих-операций.- Не включайте slowlog в
request_slowlog_timeout = 0— это выключает лог; медленные останутся загадкой.
5. Куда смотреть дальше: мост к диагностике
slowlog отвечает «что в PHP тормозит». Но если тормозит база, медленный запрос будет виден как mysqli-фрейм в том же slowlog. Дальнейший шаг — посмотреть SHOW PROCESSLIST и медленный лог сервера (статья №5 про MariaDB) и связать: «PHP-воркер висел в get_posts() → база держала долгий SELECT → нет индекса». Это метод от верхнего к нижнему (сайт → nginx → php-fpm → db → ОС), которым построена финальная статья «Диагностика: когда сервер „не дышит“». Здесь вы держите второй этаж этой лестницы.
Инструменты под задачу
Не уверены, сколько воркеров выдержит ваш VPS? Введите реальную память машины, swap и примерный размер одного PHP-процесса — калькулятор посчитает безопасный pm.max_children (и start/min_spare/max_spare для dynamic), а ИИ даст экспертное заключение именно по вашим числам, без гаданий.
Чек-лист граблей PHP-FPM
- `pm.max_children` «на глаз» больше, чем делится память на воркера → OOM-killer съедает детей (и, что страшнее, базу/SSH). Считайте по `free` и реальному RSS.
- `static` при высокой и редкой нагрузке держит детей «впрок» и жрёт память зря; `ondemand`/`dynamic` — по задаче.
- Забыли `pm.max_requests` → при утечке в плагине пул разбухает; дети не пересоздаются.
- Правки в `/etc/php/8.2/cli/php.ini` вместо `…/fpm/php.ini` → «настроил, не подействовало»: CLI и FPM имеют разные php.ini.
- `php_admin_value` и `php_value` перепутали: хотите запретить плагину менять лимит — нужен `admin`, а не мягкий `php_value`.
- `memory_limit` (на запрос) поднят до 512M при `max_children=12` → фактический пик пула ~6 ГБ → обвал.
- Ограничили лимиты только в PHP, не в nginx → снова `413`; или наоборот. Все три сита должны быть согласованы (`client_max_body_size` > `post_max_size` > `upload_max_filesize`).
- `opcache.validate_timestamps=0` без сброса после обновления → «залил — а старый код живёт». Либо держите `=1`, либо приучите себя сбрасывать кэш.
- `memory_consumption` Opcache не посчитан в бюджете: Opcache-сегмент **плюсом** к детям; суммируйте при расчёте `max_children`.
- `slowlog` на путь, которого нет или нет прав у пула → лог не пишется и вы «вслепую» чините тормоза. Создайте папку и проверьте владельца (user пула).
- `listen.mode 0666` на сокете пула → чужие процессы пишут запросы в ваш PHP; ставьте `0660` + правильный owner/group.
- Пулы плодят суммарных детей больше, чем память: суммируйте `pm.max_children` по всем пулам.
Материал написан простым языком; любой англоязычный термин (PHP-FPM, master, воркер, пул, FastCGI, RSS, pm, Opcache, байткод, warm-up, slowlog, backtrace, ondemand) объясняется при первом употреблении. Конфиги — для Ubuntu/Debian с PHP-FPM из
apt; конкретные версии и пути (8.2против8.3,php8.2-fpm.sockпротивphp8.3-fpm.sock, имя юнитаphp8.2-fpm) сознательно условны — проверяйте фактические черезphp -v,systemctl list-units | grep fpmи содержимоеpool.d/. Домены и пути условные (example.test,/var/www/example.test); подставляйте свои. После правки пула —php-fpm8.2 -tиsystemctl reload php8.2-fpm; после правкиphp.ini— рестарт пула (reloadперечитывает pool.d,php.iniобычно при restart). Тонкие механизмы (pm.status_path+ живой статус-эндпоинт FPM,emergency_restart_interval/restart_request/rlimit_*,catch_workers_output,clear_env, TCP-listen для удалённой БД,listen.allowed_clients) упомянуты лишь в контексте необходимости — это уровеньman php-fpm, когда базовый WordPress-стек уже живёт и стабилен.
👤 Зарегистрируйтесь, чтобы добавить материал в закладки.
