Предупреждение php-fpm max_children: найдите причину в логах, прежде чем добавлять воркеры

Уведомление о партнёрских ссылках v3.1
Уведомление о партнёрских ссылках v3.2 (мобильная версия)
Тестовая среда Семейство RHELAlmaLinux 9.8Учётная запись rootПроверено 2026-10-12

Предупреждение php-fpm max_children означает, что все рабочие процессы PHP заняты обработкой запросов и новые запросы попадают в очередь. На сервере WordPress с Nginx, Apache и php-fpm, где это предупреждение копилось неделями, по логам были выявлены запросы, занимавшие воркеры, и проблему решило ограничение частоты запросов в nginx, а не повышение значения настройки.

При еженедельной проверке предупреждение фиксировалось 27–187 раз в день, но за те же четыре дня посетители не получили ни одной ошибки 502·504. Воркеры занимали не посетители, а бот, раз за разом запрашивавший адреса админки, и сканер, запрашивавший несуществующие пути.

1️⃣ php-fpm max_children & seems busy

Когда за день набирается 66 предупреждений, легко решить, что воркеров не хватило 66 раз. В логе php-fpm смешаны два разных по сути предупреждения, и большинство из них — предварительные, записанные ещё до достижения лимита.

[09-Oct-2026 00:16:18] WARNING: [pool www] seems busy (you may need to increase pm.start_servers, or pm.min/max_spare_servers), spawning 8 children, there are 0 idle, and 15 total children
[09-Oct-2026 13:02:05] WARNING: [pool www] server reached pm.max_children setting (26), consider raising it

[seems busy] сообщает, что свободных воркеров стало 0 и создаётся новый процесс; при способе управления процессами dynamic это записывается даже при кратком росте запросов. [server reached pm.max_children] — момент, когда достигнут лимит в 26 процессов и создать новые нельзя; с этого момента новые запросы ждут.

Это как разница между тем, чтобы позвать в ресторане ещё одного официанта, и тем, чтобы оставить гостей ждать на улице, потому что все места заняты.

Подсчёт за один день по типам дал 58 seems busy и 6 max_children. Регулярная проверка сложила оба предупреждения и сообщила о 66, но на самом деле лимит был достигнут шесть раз.

grep '09-Oct-2026' /var/log/php-fpm/error.log | grep 'pool www' | grep -c 'seems busy'
58
grep '09-Oct-2026' /var/log/php-fpm/error.log | grep 'max_children' | cut -c2-21
09-Oct-2026 13:02:05
09-Oct-2026 13:52:10
09-Oct-2026 18:09:20
09-Oct-2026 18:42:17
09-Oct-2026 18:46:07
09-Oct-2026 19:12:44

Шесть отметок времени, которые выводит вторая команда, становятся опорой для поиска причины; на следующем шаге используются только они.

2️⃣ Почему увеличение max_children — не решение

Если поднять лимит 26, предупреждений может стать меньше, но это не изменит того, какие запросы занимали воркеры.

Обычный расчёт — разделить доступную память на средний объём памяти одного воркера. На измеренном сервере 26 воркеров использовали в среднем 131MB и максимум 214MB, и если считать по оставшейся доступной памяти, на бумаге запас для новых воркеров был.

ps -o rss= -u www -C php-fpm | awk '{s+=$1; n++; if($1>m) m=$1} END{print n, s/n/1024, m/1024, s/1024}'
26 130.579 214.27 3395.05
free -m

Четыре значения в первом выводе — число воркеров, средняя память (MB), максимальная память (MB) и сумма (MB); столбец available в выводе free — это доступная память.

Если воркеров больше, чем ядер CPU, увеличение их числа не добавляет одновременно обрабатываемых запросов, а только удлиняет время обработки каждого. Пока посетители получают 0 ошибок 502·504, повышение лимита ничего не даёт и лишь отдаёт больше процессов ботам и сканерам, которые и так занимали воркеры.

Перед повышением лимита нужно проверить, были ли запросы, занимавшие воркеры в моменты предупреждений, посетителями; порядок проверки по access.log описан в следующем разделе.

3️⃣ Поиск причины в access.log: время, IP, путь, код ответа

Выберите одну отметку предупреждения — ответ будет в access.log за эти 1–2 минуты. Порядок такой: извлечь строки по времени, подсчитать по IP, затем проверить пути и коды ответа у первых IP.

grep '09/Oct/2026:18:09' /var/log/nginx/access.log | awk '{print $1}' | sort | uniq -c | sort -rn | head
    115 140.245.x.x
     61 187.15.x.x
     17 3.36.x.x
      8 91.239.x.x
      4 79.127.x.x
      3 109.248.x.x
      3 109.248.x.x
      3 109.248.x.x
      2 79.127.x.x
      2 40.77.x.x
grep '09/Oct/2026:18:09' /var/log/nginx/access.log | awk '{print $7, $9}' | sort | uniq -c | sort -rn | head
     55 / 200
     13 /wp-login.php 404
     13 /wp-admin/plugins.php 302
     13 /wp-admin/index.php 302
     13 /wp-admin/edit.php 302
     12 /wp-admin/profile.php 302
      4 / 499
      3 /xmlrpc.php 302
      2 /xmlrpc.php 499
      2 /wp-content/themes/kinsta/images/duos/small/wheelchair.svg 404

9 октября в 18:09 за одну минуту было записано 286 запросов; 115 из них пришли с одного IP, пути были адресами админки вроде /wp-admin/profile.php, а код ответа везде — 302.

Код ответа показывает, дошёл ли запрос до PHP. 200 для кешированного HTML, который nginx отдаёт сам, а также 444·400 обходятся без PHP, а 302·404 и некешированный 200 занимают по одному воркеру. Когда адрес админки запрашивают без входа, WordPress отвечает 302 на страницу входа, и каждый такой 302 — это один запуск PHP.

Если на одном сервере два сайта и больше, строки обоих сайтов записываются вместе в общий access.log. Сначала казалось, что бот атакует первый сайт, но сверка с логами каждого сайта показала, что 22 390 из 22 391 запросов были ко второму сайту.

4️⃣ Три вида запросов, занявших воркеры: wp-admin от бота, 404 от сканера, предзагрузка кеша

Когда тот же порядок применили ко всем моментам предупреждений, оказалось, что воркеры занимали запросы трёх видов.

Первый — бот, раз за разом запрашивавший адреса админки. Один IP отправил 22 391 запрос за три часа; 9 944 из них пришлись на четыре пути, включая /wp-admin/profile.php, а поскольку каждый 302 без входа — это один запуск PHP, воркер использовался 9 944 раза.

Второй — сканер уязвимостей, запрашивавший несуществующие пути. Даже если в User-Agent написано Googlebot, это не Googlebot: IP, отправлявший 14 672 запроса в день, был виртуальным сервером Google Cloud с обратным DNS на googleusercontent.com, а у Googlebot он оканчивается на googlebot.com. Тот же IP по очереди представлялся как bingbot, Applebot, GPTBot и ClaudeBot.

Запросы к файлам вроде .env nginx обрывает кодом 444 за 0,04 секунды, но на обычные пути, которых нет в правилах блокировки, например /userfiles или /secrets.yml, WordPress генерирует страницу 404. Такая страница весит 266KB и требует 1,4–2,9 секунды — столько же, сколько генерация обычной записи, а один IP запрашивал её 4 069 раз в день.

Третий — сам сервер. При сохранении записи или перевода кеш очищается, и предзагрузка WP Rocket заново запрашивает все URL — 8 489 запросов в день, до 60 в минуту. Проверка статуса индексации Rank Math через Action Scheduler тоже занимала один воркер 754 раза в день. Оба процесса приходились только на рабочие часы и не вызывали ошибок у посетителей, поэтому их исключили из мер.

В симуляторе ниже видно, на каком уровне заканчивается каждый запрос и занимает ли он воркер PHP.

🧭 Симулятор пути запросаСтраница админки wp-adminНаглядный симулятор
🗂️ Запросы, занявшие воркеры
Запросы бота к wp-admin
Когда адрес админки запрашивают без входа, WordPress отвечает 302 на страницу входа. Каждый такой 302 — один запуск PHP.
Сканер: файлы типа .env
Запросы к файлам вроде .env правило блокировки nginx обрывает кодом 444. 0,04 секунды, PHP не задействуется.
Сканер: обычные фальшивые пути
На обычные пути, которых нет в правилах блокировки, например /userfiles или /secrets.yml, WordPress генерирует страницу 404. 266KB, 1,4–2,9 секунды — как при генерации обычной записи.
Предзагрузка кеша
При сохранении записи или перевода кеш очищается, и предзагрузка WP Rocket заново запрашивает все URL. До 60 в минуту, и каждый запрос занимает один воркер.
Визит на кешированную запись
Если файл кеша есть, nginx отвечает сам, и PHP не задействуется.
Визит на некешированную запись
Если файла кеша нет, PHP генерирует страницу и сохраняет этот HTML как файл кеша.
Общая схема
Запрос спускается сверху вниз и останавливается на уровне, где завершается ответ. Воркер PHP занимают только запросы, дошедшие до WordPress.
ЗапросГде останавливаетсяОтветВоркеры
wp-admin от ботаWordPress302 (на страницу входа)Используется 1
Сканер, файлы типа .envПравило блокировки nginx444 (0,04 с)Не используется
Сканер, обычный фальшивый путьWordPressСтраница 404 (266KB · 1,4–2,9 с)Используется 1
Предзагрузка кешаWordPress200 · сохранение в файл кешаИспользуется 1
Визит на кешированную записьФайл кеша200 (nginx отвечает сразу)Не используется
Визит на некешированную записьWordPress200 · сохранение в файл кешаИспользуется 1
Наглядный симулятор, работающий только в браузере. Ответы и время взяты из логов и замеров сервера Nginx + Apache в октябре 2026 года.

5️⃣ Ограничение частоты в nginx: лимит для запросов без входа и восстановление реального IP

Вместо блокировки выбрано ограничение частоты. Если заблокировать путь, сканер сменит путь; если заблокировать IP, на следующий день придёт другой IP. Ограничиваются только запросы без входа, доходящие до PHP: 30 в минуту и 60 одновременно на IP, а сверх лимита отдаётся 429.

Для сервера за CDN одной этой настройки недостаточно. nginx видит IP-адрес edge-узла CDN, и все посетители, идущие через этот узел, сливаются в один IP; сначала нужно прописать список IP edge-узлов в set_real_ip_from и восстановить реальный IP из X-Forwarded-For. Запросы с cookie входа исключаются из лимита, а по User-Agent исключений нет.

# контекст http
real_ip_header X-Forwarded-For;
real_ip_recursive on;
set_real_ip_from 203.0.113.0/24;   # список edge-узлов CDN
map $http_cookie $php_anon_key {
    default                 $binary_remote_addr;
    ~*wordpress_logged_in   '';
}
limit_req_zone $php_anon_key zone=php_anon:10m rate=30r/m;

# location, передающий запросы в PHP
location @apache {
    limit_req zone=php_anon burst=60 nodelay;
    limit_req_status 429;
    proxy_pass http://127.0.0.1:8080;
}

Кешированные страницы отдаются через try_files внутри nginx и до этого location не доходят, а admin-ajax.php обрабатывается в отдельном location и под лимит не попадает.

Проверять нужно параллельными запросами. Последовательные запросы не превышают 30 в минуту, потому что одна 404 занимает 1,5 секунды, и под лимит не попадают; если отправить 100 запросов по 20 одновременно, получится 67 ответов 404 и 33 ответа 429 — значит, лимит работает.

seq 1 100 | xargs -P 20 -I{} curl -s -o /dev/null -w '%{http_code}\n' https://example.com/zz-{} | sort | uniq -c

Если в выводе есть 429, лимит работает. Если IP самого сервера исключён из лимита, на время проверки снимите исключение или запустите проверку из другой сети.

6️⃣ Сравнение до и после

Лимит на адреса админки без входа включили вечером 9 октября, а лимит на запросы, доходящие до PHP, — вечером 11-го. Число ответов 302 для адресов админки упало с 9 996 за 9-е до 11 за 10-е и 1 за 11-е, а IP, отправивший 22 391 запрос за три часа, со следующего дня не прислал ни одного.

Для сканера лимит включён совсем недавно, поэтому полных суточных данных ещё нет; судить предстоит по тому, насколько снизится на следующей неделе число страниц 404, сгенерированных WordPress (2 563 за 9-е, 2 612 за 10-е, 1 630 за 11-е). Ошибок 502·504 у посетителей не было ни до, ни после.

Эти цифры подсчитаны напрямую по логам сервера WordPress на Nginx, Apache и php-fpm, а выводы сделаны не по числу предупреждений, а по ошибкам посетителей и числу запросов, доходящих до PHP.

📊До и после ограничения частотыПо дням · подсчёт по логам
ДоПосле
Ответы 302 на адреса админки
302, которые WordPress отправлял на запросы к wp-admin без входа. Лимит на адреса админки без входа включён вечером 9 октября.
02,4994,9987,4979,996
10/99,996До
10/1011После
10/111После
Запросы с IP бота
Число запросов в день с одного IP, отправившего 22 391 запрос за три часа.
05,59811,19616,79322,391
10/922,391До
10/100Нет запросов
10/110Нет запросов
Страницы 404, сгенерированные WordPress
Запросы сканера к обычным фальшивым путям. Лимит на запросы, доходящие до PHP, включён вечером 11 октября, поэтому оценка — по цифрам проверки на следующей неделе.
06531,3061,9592,612
10/92,563До
10/102,612До
10/111,630До · включено вечером 11-го
Цифры — подсчёт nginx access.log по дням с 9 по 11 октября 2026 года.

🔢 FAQ & рекомендуемые материалы

dynamic создаёт и останавливает воркеры в зависимости от нагрузки и пишет предупреждение seems busy, когда свободных воркеров становится 0. static всегда держит max_children воркеров, поэтому нет задержки на их создание и нет предупреждений, но память занята постоянно. На сервере из этой статьи оставлен dynamic с 26 воркерами; если памяти с запасом, а нагрузка ровная весь день, лучше подойдёт static.

Включайте его, когда в моменты предупреждений отдельные запросы, похоже, выполняются слишком долго. Если в настройках пула указать путь [slowlog] и [request_slowlog_timeout], для запросов, превысивших это время, будет записываться место выполнения. В этой статье причиной было число запросов, поэтому хватило подсчёта по access.log, и slowlog не понадобился.

Лимит действует только на запросы без входа, которые обходят кеш и доходят до PHP, и 429 получает лишь превышение, когда один IP отправляет больше 30 запросов в минуту или больше 60 одновременно. По итогам 11 октября этот порог превысил только один IP — сканер, а состояние превышения снимается в течение минуты, и следующий запрос получает обычный ответ.

Нет, её не сокращали. Предзагрузка приходилась только на рабочие часы, когда сохранялись записи и переводы, и не приводила к ошибкам у посетителей. Ориентир — не число предупреждений, а то, получали ли посетители ошибки 502·504; если их 0, предзагрузку оставляют как есть.

ℹ️ О партнёрских ссылках
Материалы этого сайта содержат партнёрские ссылки. Если посетитель покупает товар или услугу по такой ссылке, сайт получает комиссию от продавца. При этом сумма, которую платит покупатель (при акционных скидках сумма снижается ↓), не увеличивается. Указанные цены, скидки и наличие актуальны на момент написания и могут отличаться, поэтому перед покупкой уточняйте их у продавца. Товары выбираются и оцениваются по нашим собственным критериям, а комиссия не влияет ни на порядок, ни на содержание обзоров.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Докажите, что вы человек: 2   +   2   =