Ostrzeżenie php-fpm max_children: ustal przyczynę w logach, zanim dodasz workery

Ostrzeżenie php-fpm max_children oznacza, że wszystkie procesy workerów PHP obsługują żądania, więc nowe trafiły do kolejki. Na serwerze WordPress z Nginx, Apache i php-fpm, gdzie to ostrzeżenie narastało od tygodni, wskazałem w logach żądania zapełniające workery i rozwiązałem problem limitem szybkości w nginx zamiast podnosić ustawienie.
W cotygodniowej kontroli ostrzeżenie zapisywało się 27–187 razy dziennie, a mimo to przez te same cztery dni odwiedzający nie dostali ani jednego błędu 502 czy 504. Workery zapełniali nie odwiedzający, lecz bot zasypujący adresy administracyjne i skaner żądający nieistniejących ścieżek.
1️⃣ php-fpm max_children & seems busy
Gdy ostrzeżeń jest 66 dziennie, łatwo odczytać to jako 66 braków workerów. Log php-fpm miesza dwa różne ostrzeżenia, a większość to wczesne ostrzeżenia zapisywane, zanim limit zostanie osiągnięty.
[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] oznacza, że wolnych workerów jest 0 i tworzony jest nowy proces; przy zarządzaniu procesami dynamic zapisuje się to nawet przy krótkim wzroście żądań. [server reached pm.max_children] to moment osiągnięcia limitu 26, gdy nie dało się utworzyć kolejnych, i od tej chwili nowe żądania czekają.
To różnica między zawołaniem jeszcze jednego kelnera a zostawieniem gości na zewnątrz, bo wszystkie miejsca są zajęte.
Zliczenie jednego dnia według typu dało 58 seems busy i 6 max_children. Rutyna kontrolna zsumowała oba i zgłosiła 66, ale limit został faktycznie osiągnięty sześć razy.
grep '09-Oct-2026' /var/log/php-fpm/error.log | grep 'pool www' | grep -c 'seems busy'58grep '09-Oct-2026' /var/log/php-fpm/error.log | grep 'max_children' | cut -c2-2109-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:44Sześć godzin wypisanych przez drugie polecenie staje się punktem odniesienia do śledzenia przyczyny; następny krok używa tylko tych godzin.
2️⃣ Dlaczego podniesienie max_children nie jest rozwiązaniem
Podniesienie limitu 26 może zmniejszyć liczbę ostrzeżeń, ale nie zmienia tego, jakie żądania zapełniły workery.
Typowe wyliczenie dzieli dostępną pamięć przez średnią pamięć jednego workera. Na mierzonym serwerze 26 workerów zużywało średnio 131MB, a maksymalnie 214MB, i licząc z pozostałej dostępnej pamięci, na papierze był jeszcze zapas na kolejne workery.
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.05free -mCztery wartości w pierwszym wyniku to liczba workerów, średnia pamięć (MB), maksymalna pamięć (MB) i suma (MB); kolumna available w wyniku free to dostępna pamięć.
Gdy workerów jest więcej niż rdzeni CPU, kolejne workery nie zwiększają liczby żądań obsługiwanych naraz; tylko wydłużają czas każdego. Dopóki odwiedzający nie dostają żadnych błędów 502 czy 504, podniesienie limitu nic nie daje, a jedynie przydziela więcej procesów botom i skanerom zajmującym workery.
Przed podniesieniem limitu sprawdź, czy żądania zajmujące workery w chwilach ostrzeżeń pochodziły od odwiedzających, czy nie; następna część pokazuje, jak to sprawdzić w access.log.
3️⃣ Śledzenie przyczyny w access.log: czas, IP, ścieżka, kod odpowiedzi
Wybierz jedną chwilę ostrzeżenia, a odpowiedź znajdziesz w access.log z tych 1–2 minut. Kolejność: wyciągnij wiersze po czasie, zlicz według IP, a potem sprawdź ścieżki i kody odpowiedzi czołowych 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.xgrep '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 4049 października o 18:09 w ciągu minuty zapisano 286 żądań; jedno IP odpowiadało za 115 z nich, ścieżki były adresami administracyjnymi jak /wp-admin/profile.php, a wszystkie kody odpowiedzi to 302.
Kod odpowiedzi mówi, czy żądanie dotarło do PHP. 200 dla HTML z cache podanego bezpośrednio przez nginx oraz 444 czy 400 nigdy nie dotykają PHP, a 302, 404 i 200 bez cache zużywają po jednym workerze. Gdy ktoś bez logowania żąda adresu administracyjnego, WordPress odpowiada 302 do strony logowania, a to jedno 302 to jedno wykonanie PHP.
Jeśli na jednym serwerze są dwie lub więcej stron, globalny access.log zapisuje wiersze ich wszystkich. Najpierw sądziłem, że ten bot atakuje pierwszą stronę, ale porównanie z logami poszczególnych stron pokazało, że 22 390 z 22 391 żądań dotyczyło drugiej strony.
4️⃣ Trzy rodzaje żądań, które zapełniły workery: wp-admin bota, 404 skanera, preload cache
Zastosowanie tej samej kolejności do wszystkich chwil ostrzeżeń pokazało trzy rodzaje żądań zapełniających workery.
Pierwszym był bot, który w kółko żądał adresów administracyjnych. Jedno IP wysłało w trzy godziny 22 391 żądań; 9 944 z nich trafiło do czterech ścieżek, w tym /wp-admin/profile.php, a ponieważ każde 302 bez logowania to jedno wykonanie PHP, zajął workera 9 944 razy.
Drugim był skaner luk żądający nieistniejących ścieżek. Nawet z Googlebot w User-Agent to nie jest Googlebot: IP, które wysyłało 14 672 żądania dziennie, było wirtualnym serwerem Google Cloud z odwrotnym DNS kończącym się na googleusercontent.com, a Googlebot kończy się na googlebot.com. To samo IP na zmianę używało nazw bingbot, Applebot, GPTBot i ClaudeBot.
Żądania plików typu .env nginx ucina kodem 444 w 0,04 sekundy, ale zwykłe ścieżki spoza reguł blokady, jak /userfiles czy /secrets.yml, zmuszają WordPress do wygenerowania strony 404. Ta strona 404 kosztuje 266KB i 1,4–2,9 sekundy, tyle co wygenerowanie zwykłego wpisu, a jedno IP żądało jej 4 069 razy dziennie.
Trzecim był sam serwer. Zapisanie wpisu lub tłumaczenia opróżnia cache, a preload WP Rocket ponownie żąda wszystkich adresów, co dało 8 489 żądań dziennie i do 60 na minutę. Sprawdzanie stanu indeksowania przez Rank Math też zajmowało workera 754 razy dziennie przez Action Scheduler. Oba skupiały się w godzinach pracy i nie powodowały błędów u odwiedzających, więc pominąłem je w naprawie.
W symulatorze poniżej widać, na której warstwie kończy się każde żądanie i czy zużywa workera PHP.
| Żądanie | Zatrzymuje się na | Odpowiedź | Workery |
|---|---|---|---|
| wp-admin bota | WordPress | 302 (do strony logowania) | Zużywa 1 |
| Skaner typu .env | Reguła blokady nginx | 444 (0,04 s) | Nieużywane |
| Skaner zwykła fałszywa ścieżka | WordPress | Strona 404 (266KB · 1,4–2,9 s) | Zużywa 1 |
| Preload cache | WordPress | 200 · zapis jako plik cache | Zużywa 1 |
| Wizyta we wpisie z cache | Plik cache | 200 (nginx odpowiada bezpośrednio) | Nieużywane |
| Wizyta we wpisie bez cache | WordPress | 200 · zapis jako plik cache | Zużywa 1 |
5️⃣ Limit szybkości w nginx: ograniczanie żądań bez logowania i przywracanie prawdziwego IP
Wybrałem limit szybkości zamiast blokady. Zablokujesz ścieżkę, a skaner zmieni ścieżkę; zablokujesz IP, a następnego dnia pojawi się inne. Ograniczane są tylko żądania bez logowania docierające do PHP, do 30 na minutę i 60 naraz na IP, a nadwyżka dostaje 429.
Dla serwera za CDN samo to ustawienie nie wystarczy. IP widziane przez nginx to adres edge'a CDN, więc wszyscy odwiedzający przechodzący przez ten edge zlewają się w jedno IP; najpierw trzeba zarejestrować listę IP edge'ów przez set_real_ip_from, aby przywrócić prawdziwe IP z X-Forwarded-For. Żądania z ciasteczkiem logowania są zwolnione, ale nic nie jest zwalniane na podstawie User-Agent.
# kontekst http
real_ip_header X-Forwarded-For;
real_ip_recursive on;
set_real_ip_from 203.0.113.0/24; # lista edge'ów 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 przekazująca do PHP
location @apache {
limit_req zone=php_anon burst=60 nodelay;
limit_req_status 429;
proxy_pass http://127.0.0.1:8080;
}Strony z cache obsługuje w nginx try_files i nigdy nie trafiają do tej location, a admin-ajax.php to osobna location, więc nie podlega limitowi.
Testuj równoczesnymi żądaniami. Żądania po kolei nigdy nie przekroczą 30 na minutę, bo jedno 404 trwa 1,5 sekundy, więc nie trafiają w limit; wysłanie 100 żądań po 20 naraz daje 67 odpowiedzi 404 i 33 odpowiedzi 429, co potwierdza działanie limitu.
seq 1 100 | xargs -P 20 -I{} curl -s -o /dev/null -w '%{http_code}\n' https://example.com/zz-{} | sort | uniq -cJeśli wynik liczy 429, limit działa. Jeśli zwolniłeś własne IP serwera, na czas testu na chwilę zdejmij zwolnienie albo uruchom go z innej sieci.
6️⃣ Przed i po
Limit na adresy administracyjne bez logowania zaczął działać wieczorem 9 października, a limit na żądania docierające do PHP wieczorem 11. Liczba 302 dla adresów administracyjnych spadła z 9 996 dziewiątego do 11 dziesiątego i 1 jedenastego, a IP, które w trzy godziny wysłało 22 391 żądań, od następnego dnia nie wysłało żadnego.
W przypadku skanera limit dopiero wszedł w życie, więc nie ma jeszcze liczb z pełnego dnia; ocena przyjdzie z tego, jak bardzo w przyszłotygodniowej kontroli spadnie liczba stron 404 generowanych przez WordPress (2 563 dziewiątego, 2 612 dziesiątego, 1 630 jedenastego). Błędy 502 i 504 u odwiedzających wynosiły 0 przed i po.
Te liczby zliczono bezpośrednio z logów serwera WordPress z Nginx, Apache i php-fpm, a ocena opierała się nie na liczbie ostrzeżeń, lecz na błędach odwiedzających i żądaniach docierających do PHP.
🔢 FAQ & polecane treści
Cache
Cache witryny & wewnętrzny cache serwera
Wyjaśnia warstwa po warstwie, jak żądania z cache omijają PHP.
Bezpieczeństwo
Luki bezpieczeństwa wp-config.php
Jak chronić uprawnieniami i regułami serwera plik konfiguracyjny, w który celują skanery.
Linux
15 kategorii poleceń Linux
Tabela dzieląca polecenia na 15 kategorii, w tym grep i awk użyte do zliczania logów.
ℹ️ Informacja o linkach partnerskich
Treści tej witryny zawierają linki partnerskie. Jeśli odwiedzający kupi produkt lub usługę przez taki link, witryna otrzymuje od sprzedawcy prowizję. Kwota płacona przy tym przez kupującego (podczas promocji spada↓) nie wzrasta. Podane ceny, rabaty i dostępność odnoszą się do chwili pisania i mogą różnić się od stanu faktycznego, dlatego przed zakupem sprawdź je u sprzedawcy. Produkty wybieramy i oceniamy według własnych kryteriów, a prowizja nie wpływa ani na kolejność prezentacji, ani na treść ocen.