Коли WordPress «приклеює» процесор на 100%, це вже не схоже на «налаштування продуктивності». Це відчуття, ніби ваш сайт плавиться на очах, а ви намагаєтеся пояснити нетехнічній людині, що так — сервер «в мережі», але ні — він фактично не працює.
Добра новина: стрибки CPU зазвичай можна діагностувати. Погана новина: люди зазвичай здогадуються. Вимикають випадкові плагіни, перезапускають PHP-FPM як ритуал і оголошують перемогу до наступного стрибка. Давайте робити це як у продакшені: вимірюємо, атрибутуємо, ухвалюємо рішення і виправляємо, не ламаючи оформлення замовлення.
Швидкий план діагностики (робіть це спочатку)
Це шлях «зупинити кровотечу і виявити нападника». Не оптимізуйте. Не рефакторте. Не сперечайтеся з графіком. Слідуйте ланцюжку доказів від CPU до процесу, від процесу до запиту та далі до коду.
1) Підтвердіть, що це реальна насиченість CPU (не лише «load»)
- Перевірте CPU, чергу виконання, steal time (VM можуть брехати, коли сусіди шумлять).
- Рішення: якщо %steal високий, ваша аплікація може бути невинною; проблема у гіпервізорі.
2) Визначте, яка родина процесів спалює CPU
- Це php-fpm воркери? Один процес ут runaway? mysqld? Щось інше?
- Рішення: якщо PHP гарячий — далі атрибуція запиту. Якщо MySQL гарячий — переходьте до повільних запитів.
3) Прив’яжіть CPU до шляху запиту та клієнта
- Корелюйте PHP-FPM slowlog, журнали доступу Nginx/Apache і топ IP/URL.
- Рішення: якщо домінує один URL або одна IP, пом’якшіть на краю (WAF/обмеження швидкості) під час детального розслідування.
4) Знайдіть точку входу WordPress
- Типові підозрювані:
wp-login.php,xmlrpc.php,/wp-json/,admin-ajax.php, кінцеві точки WooCommerce, пошук, генерація sitemap. - Рішення: якщо це точки аутентифікації — вважайте трафік ворожим. Якщо це admin-ajax — розглядайте як поведінку плагіна/теми, доки не доведено, що це бот.
5) Визначте плагін/тему або запит
- Використовуйте PHP-FPM slowlog зі стек-трейсами та MySQL slow query log.
- Рішення: відключіть/замініть плагін, змініть конфіг або агресивно кешуйте — на підставі доказів, а не здогадок.
6) Застосуйте безпечне тимчасове обмеження
- Обмежуйте швидкість для зловмисних шляхів, увімкніть кешування і встановіть розумні ліміти PHP-FPM.
- Рішення: обирайте контрольоване деградування (429 для шкідливих кінцевих точок) замість повного падіння сайту.
Що насправді означає «100% CPU» для WordPress
У світі WordPress «CPU на 100%» зазвичай означає, що PHP робить занадто багато роботи занадто часто. Це також може означати, що занадто багато PHP-воркерів готові до виконання одночасно, бо плагін спровокував дорогі запити або віддалені виклики. Або це значить, що MySQL горить, а PHP просто чекає — але ваші графіки занадто грубі, щоб це показати.
Кілька перевірок реальності:
- Один ядро завантажене на багатоядерній машині може все одно бути катастрофою, якщо PHP-FPM однонитковий на запит і ваша найгарячіша кінцева точка серіалізує роботу (блокування, сесії, cache stampedes).
- Load average — це не те саме, що CPU. Load може означати I/O wait, чергу виконання або завислі процеси. Перевіряйте iowait і run queue.
- «Але у нас є кеш» не означає, що ви в безпеці. Залоговані користувачі, кошики/чек-аут, admin-ajax і персоналізовані сторінки за замовчуванням обходять більшість кешів сторінок.
Перефразована ідея від Werner Vogels (CTO Amazon): Все рано чи пізно виходить з ладу; проектуйте так, щоб можна було виявити, обмежити зону ураження та швидко відновитися.
І так, інколи причина смішно буденна: cron, що запускається щохвилини, бо хтось скопіював «оптимізаційний» фрагмент з форуму.
Короткий жарт #1: WordPress не «випадково» сідає на 100% CPU. Він просто дуже відданий хаосу, який ви дозволили.
Цікаві факти та контекст (коротка корисна історія)
- WordPress запустили в 2003 році як форк b2/cafelog; він успадкував модель «PHP рендерить все на запит», через що вартість кожного запиту має значення.
- wp-cron за замовчуванням — не справжній cron. Він тригериться трафіком сайту, що означає: «більше відвідувачів» може ненавмисно означати «більше запусків cron».
- xmlrpc.php був корисністю для сумісності (віддалена публікація, мобільні клієнти), але потім став улюбленою ціллю для credential stuffing і pingback-амліфікації.
- admin-ajax.php став швейцарським ножем для плагінів через зручність; він також випадково генерує навантаження, коли використовується для частого фронтенд-пулінгу.
- PHP-FPM замінив mod_php як поширений варіант розгортання, бо ізольовував пулі та покращував стабільність, але це також зробило помилки в max_children простішими у масштабі.
- Об’єктне кешування змістило вузькі місця: додавання Redis/Memcached може знизити навантаження на MySQL, але також сховати поганий код, поки cache miss або евікції не спричинять stampede.
- WooCommerce змінив форму трафіку: кошики, сесії, AJAX-фрагменти і поведінка залогованих користувачів більше анулюють кеш ніж звичайний блог.
- HTTP/2 зменшив накладні витрати на з’єднання, але може збільшити конкурентність запитів, що робить дорогі кінцеві точки ламкими, якщо не встановлювати обмеження швидкості.
- «Headless» WP збільшив використання API: активний трафік
/wp-json/може діяти як низькоінтенсивний DDoS, якщо запити необмежені або некешовані.
Практичні завдання: команди, виводи, рішення (основний набір інструментів)
Вам потрібні повторювані завдання, які переводять вас від «CPU плохо» до «цей плагін і цей кінцевий пункт, з цих IP, з цією частотою». Нижче — завдання, які можна виконати на типовому Linux VPS або VM з Nginx/Apache + PHP-FPM + MySQL/MariaDB. Кожне містить: команду, приклад виводу, що це означає та яке рішення прийняти.
Завдання 1: Підтвердіть насиченість CPU проти steal time
cr0x@server:~$ mpstat -P ALL 1 5
Linux 6.5.0 (wp-prod-01) 12/27/2025 _x86_64_ (4 CPU)
12:01:11 PM CPU %usr %nice %sys %iowait %irq %soft %steal %idle
12:01:12 PM all 92.10 0.00 6.40 0.20 0.00 0.80 0.00 0.50
12:01:12 PM 0 99.00 0.00 1.00 0.00 0.00 0.00 0.00 0.00
12:01:12 PM 1 88.00 0.00 11.00 1.00 0.00 0.00 0.00 0.00
12:01:12 PM 2 92.00 0.00 7.00 1.00 0.00 0.00 0.00 0.00
12:01:12 PM 3 89.00 0.00 10.00 1.00 0.00 0.00 0.00 0.00
Значення: Високий %usr з майже нульовим %steal означає, що ваше навантаження фактично спалює CPU. Низький %idle підтверджує насичення.
Рішення: Продовжуйте розслідування в аплікації. Якщо %steal був високим (>5–10%), ескалюйте до рівня хостингу або мігруйте.
Завдання 2: Знайдіть процеси, що зараз споживають CPU
cr0x@server:~$ ps -eo pid,ppid,cmd,%cpu,%mem --sort=-%cpu | head -n 12
PID PPID CMD %CPU %MEM
19421 19310 php-fpm: pool www 88.4 2.1
19455 19310 php-fpm: pool www 72.9 2.0
19470 19310 php-fpm: pool www 65.1 2.1
19310 1 php-fpm: master process 2.1 0.4
2214 1 nginx: worker process 1.3 0.3
1870 1 mysqld 0.9 10.5
Значення: PHP-FPM воркери — джерело навантаження, не Nginx. MySQL поки не основна гаряча точка.
Рішення: Перейдіть до атрибуції PHP-запитів: slowlog, журнали доступу та таймінги upstream.
Завдання 3: Перевірте насиченість пулу PHP-FPM (чи утворюється черга?)
cr0x@server:~$ sudo ss -s
Total: 817 (kernel 0)
TCP: 613 (estab 402, closed 162, orphaned 0, synrecv 0, timewait 162/0), ports 0
Transport Total IP IPv6
RAW 0 0 0
UDP 6 4 2
TCP 451 380 71
INET 457 384 73
FRAG 0 0 0
Значення: Багато встановлених TCP-сесій може означати нормальний трафік… або повільні відповіді, що накопичуються.
Рішення: Перевірте таймінги upstream Nginx і статус PHP-FPM далі.
Завдання 4: Увімкніть і прочитайте статус PHP-FPM (якщо доступно)
cr0x@server:~$ curl -s http://127.0.0.1/fpm-status | head
pool: www
process manager: dynamic
start time: 27/Dec/2025:11:02:13 +0000
start since: 3540
accepted conn: 918245
listen queue: 37
max listen queue: 221
listen queue len: 128
Значення: Ненульовий listen queue означає, що запити чекають вільного PHP-воркера. Це видимо для користувача затримка.
Рішення: Тимчасово можете підняти pm.max_children, якщо дозволяє RAM, але спочатку знайдіть, чому воркери повільні (дорогий код, віддалені виклики, БД). Масштабування поганої роботи лише збільшує погану роботу.
Завдання 5: Перегляньте топ URL в Nginx за кількістю запитів
cr0x@server:~$ sudo awk '{print $7}' /var/log/nginx/access.log | sort | uniq -c | sort -nr | head
9412 /wp-admin/admin-ajax.php
3120 /wp-login.php
1788 /wp-json/wp/v2/posts?per_page=100
904 /?s=shoes
611 /xmlrpc.php
Значення: admin-ajax.php домінує. Це рідко «нормальне оглядання». Зазвичай це фіча плагіна, скрипт теми або бот, що допитує кінцеві точки.
Рішення: Визначте, який action= гарячий, потім зіставте його з плагіном.
Завдання 6: Розбити admin-ajax по параметру action
cr0x@server:~$ sudo grep "admin-ajax.php" /var/log/nginx/access.log | awk -F'action=' '{print $2}' | awk '{print $1}' | cut -d'&' -f1 | sort | uniq -c | sort -nr | head
8122 wc_fragment_refresh
901 elementor_ajax
214 wpforms_submit
143 heartbeat
Значення: wc_fragment_refresh — класичний виклик оновлення фрагментів корзини WooCommerce. Він може бути легітимним, але також відомий як «балакучий» і шкодить кешу.
Рішення: Якщо це реальні покупці — оптимізуйте шлях WooCommerce і правила кешу. Якщо це боти — обмежуйте/відхиляйте за поведінкою.
Завдання 7: Визначте топ IP, що б’ють по кінцевій точці
cr0x@server:~$ sudo grep "admin-ajax.php" /var/log/nginx/access.log | awk '{print $1}' | sort | uniq -c | sort -nr | head
3022 203.0.113.50
1877 198.51.100.77
944 192.0.2.19
611 10.0.0.12
Значення: Одна IP, що генерує тисячі запитів, викликає підозру, якщо це не ваш моніторинг, тест навантаження чи відомий реверс-проксі.
Рішення: Якщо це не ваші проксі — блокуйте або обмежуйте негайно на краю або фаєрволі. Потім продовжуйте діагностику для решти трафіку.
Завдання 8: Підтвердіть, що запити повільні в таймінгах upstream Nginx
cr0x@server:~$ sudo awk '{print $(NF-1),$7,$1}' /var/log/nginx/access.log | head -n 5
0.842 /wp-admin/admin-ajax.php 203.0.113.50
1.102 /wp-admin/admin-ajax.php 203.0.113.50
0.019 /wp-login.php 198.51.100.77
0.611 /wp-admin/admin-ajax.php 192.0.2.19
0.955 /wp-json/wp/v2/posts?per_page=100 198.51.100.77
Значення: Якщо ви налаштували формат логу, що включає upstream response time, ви побачите, чи бекенд повільний. Значення близько 1с для admin-ajax при великій кількості запитів швидко «запікають» CPU.
Рішення: Повільний бекенд означає: потрібні PHP slowlog і профілювання WP, а не лише блокування.
Завдання 9: Увімкніть PHP-FPM slowlog і зловіть стек-трейси
cr0x@server:~$ sudo grep -nE "request_slowlog_timeout|slowlog" /etc/php/8.2/fpm/pool.d/www.conf
308:request_slowlog_timeout = 5s
309:slowlog = /var/log/php-fpm/www-slow.log
Значення: Запити, що тривають більше 5 секунд, викинуть backtrace у файл slowlog.
Рішення: Увімкніть під час інциденту. Якщо не можете дозволити накладні витрати на логування — встановіть вище (10–15с) і тримайте тимчасово.
cr0x@server:~$ sudo tail -n 25 /var/log/php-fpm/www-slow.log
[27-Dec-2025 12:03:41] [pool www] pid 19455
script_filename = /var/www/html/wp-admin/admin-ajax.php
[0x00007f2a1c8b2a40] mysqli_query() /var/www/html/wp-includes/wp-db.php:2345
[0x00007f2a1c8b28b0] _do_query() /var/www/html/wp-includes/wp-db.php:2263
[0x00007f2a1c8b27f0] query() /var/www/html/wp-includes/wp-db.php:3307
[0x00007f2a1c8b2460] get_results() /var/www/html/wp-includes/wp-db.php:3650
[0x00007f2a1c8b1f90] wc_get_products() /var/www/html/wp-content/plugins/woocommerce/includes/wc-product-functions.php:1201
[0x00007f2a1c8b1c00] my_custom_fragments() /var/www/html/wp-content/plugins/some-fragments/plugin.php:88
Значення: Тепер у вас є шлях від гарячої кінцевої точки до конкретного файлу плагіна і функції. Це золото.
Рішення: Вимкніть/замініть плагін або відкоригуйте його налаштування. Якщо це ваш кастомний код — виправте його. Якщо це поведінка ядра WooCommerce — розгляньте фрагментне кешування або зменшення частоти викликів.
Завдання 10: Використайте WP-CLI щоб перелічити плагіни та їх статус
cr0x@server:~$ cd /var/www/html && sudo -u www-data wp plugin list --status=active
+---------------------+----------+-----------+---------+
| name | status | update | version |
+---------------------+----------+-----------+---------+
| woocommerce | active | available | 8.9.2 |
| elementor | active | none | 3.24.0 |
| some-fragments | active | none | 1.6.1 |
| redis-cache | active | none | 2.5.3 |
+---------------------+----------+-----------+---------+
Значення: Підтверджує, що підозрюваний плагін активний і показує стан оновлення (інколи ви працюєте на відомо поганій версії).
Рішення: Якщо є оновлення з виправленнями продуктивності/безпеки — заплануйте його. Якщо потрібне негайне полегшення — деактивуйте винуватця в низький трафік або на канарі.
Завдання 11: Тимчасово відключіть підозрілий плагін (контрольований тест)
cr0x@server:~$ cd /var/www/html && sudo -u www-data wp plugin deactivate some-fragments
Plugin 'some-fragments' deactivated.
Значення: Видаляє шлях коду з продакшну без прямого редагування файлів.
Рішення: Спостерігайте CPU і частоту запитів. Якщо CPU падає і помилок не з’являється — ви знайшли підозрюваного. Якщо з’являються помилки — відкотіться і спробуйте більш вузьке пом’якшення (rate limit, кеш, feature toggle).
Завдання 12: Перевірте MySQL на повільні запити (чи БД справжній вузький профіль?)
cr0x@server:~$ sudo mysql -e "SHOW FULL PROCESSLIST\G" | head -n 30
*************************** 1. row ***************************
Id: 12891
User: wpuser
Host: 127.0.0.1:51862
db: wordpress
Command: Query
Time: 9
State: Sending data
Info: SELECT SQL_CALC_FOUND_ROWS wp_posts.ID FROM wp_posts WHERE 1=1 AND wp_posts.post_type IN ('product') AND (wp_posts.post_status = 'publish') ORDER BY wp_posts.post_date DESC LIMIT 0, 48
Значення: Запити, що тривають 9 секунд — це погано. Sending data часто означає великі сканування, погані індекси або величезні набори результатів.
Рішення: Увімкніть slow query log, дослідіть шаблони запитів і виправте індекси або зменшіть витрати запиту (пагінація, обмеження, кешування). Розгляньте обмеження дорогих API-запитів.
Завдання 13: Увімкніть slow query log MySQL (тимчасово) і проаналізуйте
cr0x@server:~$ sudo mysql -e "SET GLOBAL slow_query_log = 'ON'; SET GLOBAL long_query_time = 1;"
cr0x@server:~$ sudo tail -n 20 /var/log/mysql/slow.log
# Time: 2025-12-27T12:06:01.123456Z
# User@Host: wpuser[wpuser] @ localhost []
# Query_time: 2.941 Lock_time: 0.001 Rows_sent: 48 Rows_examined: 504812
SET timestamp=1766837161;
SELECT SQL_CALC_FOUND_ROWS wp_posts.ID FROM wp_posts WHERE 1=1 AND wp_posts.post_type IN ('product') ORDER BY wp_posts.post_date DESC LIMIT 0, 48;
Значення: Rows_examined у сотнях тисяч для невеликого результату — класичний приклад «вичищаєте океан, щоб знайти рибку».
Рішення: Визначте, яка сторінка/API це запускає, потім розгляньте перепис запиту через налаштування плагіна, додавання індексів (обережно) або кешування на рівні об’єктів/сторінки.
Завдання 14: Перевірте очікування дискового I/O (CPU може «бути зайнятим» деінде)
cr0x@server:~$ iostat -x 1 3
Device r/s w/s rkB/s wkB/s await %util
nvme0n1 2.1 18.2 44.0 512.0 1.20 22.5
Значення: Низький await і помірне %util означають, що сховище, ймовірно, не є вузьким місцем. Якщо await високий (десятки/сотні мс) і %util на максимумі — ваша «CPU-проблема» насправді тиск I/O.
Рішення: Якщо сховище — вузьке місце, виправляйте I/O (оптимізуйте БД, перемістіть на швидший диск, зменшіть логування, налаштуйте буфер пул) перед тим, як торкатися PHP.
Завдання 15: Зловіть живий backtrace PHP-воркера за допомогою perf (коли відчаю)
cr0x@server:~$ sudo perf top -p 19421
Samples: 2K of event 'cpu-clock', 4000 Hz, Event count (approx.): 510000000
Overhead Shared Object Symbol
18.20% php-fpm8.2 zend_execute_ex
11.35% php-fpm8.2 zif_preg_match
8.74% libpcre2-8.so.0.11.2 pcre2_match_8
6.10% php-fpm8.2 zim_spl_autoload_call
Значення: Сильне навантаження на regex (preg_match) всередині PHP може бути поведінкою плагіна (фільтрація, парсинг контенту, сканування безпеки) або теми, що робить «хитрі» речі на кожному запиті.
Рішення: Поєднайте з slowlog, щоб визначити, який плагін це викликає. Якщо плагін безпеки сканує контент на кожному запиті — можливо, настав час шукати спокійніший продукт.
Плагін, бот чи платформа?
Більшість інцидентів CPU потрапляють у одну з трьох категорій:
- Ворожий трафік: credential stuffing на
wp-login.php, зловживання XML-RPC, brute force на REST-контактах, агресивне скрейпінг або «SEO‑інструменти», що поводяться як locust. - Поведінка плагіна/теми: polling через admin-ajax, дорогі запити товарів, обробка зображень, аналітичні маячки, page builders, що роблять серверний рендеринг або динамічну генерацію CSS.
- Обмеження платформи: замало ядер CPU, замало RAM (thrash у swap), steal time від сусідів, неправильно налаштований PHP-FPM, недопроізведений MySQL.
Ключовий крок — атрибутувати за шляхом запиту і за клієнтом. Проблема плагіна проявляється як багато різних IP, що звертаються до одного й того ж кінцевого пункту. Проблема бота часто має невелику кількість IP/ASN, дивні user-agent-и або високі швидкості запитів з низькою сесією протяжністю.
Тест на два хвилини
Якщо гарячою кінцевою точкою є wp-login.php або xmlrpc.php, вважайте трафік ворожим доти, доки не доведено протилежне. Якщо гарячою є admin-ajax.php з постійним action — вважайте це поведінкою плагіна/теми, доки не доведете, що бот. Якщо це /wp-json/ з per_page=100 або необмеженими запитами — вважайте це проблемою дизайну API.
Короткий жарт #2: Якщо ваша «маркетинг-автоматизація» б’є по admin-ajax.php 50 разів на секунду, це не автоматизація; це маленький denial-of-service з бюджетом.
Три міні-історії з корпоративного життя (реальні випадки)
Міні-історія 1: Інцидент через неправильне припущення
Середня компанія використовувала WordPress як публічне обличчя продуктового набору. Їхня SRE-рута вважала його «доволі статичним», тож поставили CDN спереду, увімкнули кешування сторінок і перейшли до інших задач. Все було добре. Аж поки не настав вівторок з знайомим запахом: графіки, що виглядають як обриви.
CPU був на максимумі, черга PHP-FPM росла, а головна сторінка була повільною. Перше припущення — «CDN впав». Ні, не впав. Відношення хітів кешу було відмінним. Ось чому інцидент був збиваючим: публічні сторінки були кешовані, тож чому PHP помирає?
Відповідь була в журналах доступу. Ботнет шалено бив по /wp-json/wp/v2/users і /wp-json/wp/v2/posts з великими розмірами сторінок і параметрами пошуку. CDN відправляв такі запити далі, бо вони виглядали як «звичайні GET». Команда припустила, що «GET безпечний» і «CDN == щит». Жодне з цих припущень не було вірним.
Фікс був нудним і ефективним: обмежили швидкість REST-кінцевих точок на краю, додали жорсткіші заголовки кешування там, де безпечно, і заблокували кінцеві точки перебору користувачів. Також обмежили per_page і вимкнули непотрібні маршрути. CPU впав миттєво, і урок закріпився: площа атаки включає кожен динамічний кінцевий пункт, а не лише форми входу.
Міні-історія 2: Оптимізація, що відкотилася
Інша організація регулярно мала стрибки CPU під час запусків кампаній. Хтось запропонував «легку перемогу»: збільшити PHP-FPM pm.max_children і з’єднання MySQL, щоб сервер «витримував більше конкуруючих з’єднань». Це звучало розумно і поліпшило графіки приблизно на годину.
Потім сайт став повільнішим. Не просто повільнішим — хаотичним. Деякі запити повертали швидко; інші йшли вічно. CPU лишався високим, load average ріс, і MySQL почав показувати довгі запити. Зрештою машина почала свопити, і ядро почало вбивати процеси. Команда успішно масштабувала своє вузьке місце до повного краху системи.
Корінь проблеми: збільшили PHP-конкурентність без зменшення вартості запиту. Більше воркерів означало більше одночасних дорогих запитів, що переповнили buffer pool БД. Додатковий тиск пам’яті штовхнув систему у своп, перетворивши «CPU-проблему» на «проблему всього».
Справжній фікс був менш гламурним: обмежити PHP-воркерів під RAM, увімкнути PHP-FPM slowlog, знайти плагін, що генерує неіндексовані запити товарів, і змінити його поведінку. Після цього вони могли обережно підвищувати конкуренцію. Висновок: «більше воркерів» — не оптимізація, а множник проблем.
Міні-історія 3: Нудна, але правильна практика, що врятувала день
Глобальний бренд мав WordPress для підтримки великої прес-події. Їх вже підпалювали раніше, тож вони дотримувалися нудного регламенту: тижневий інвентар плагінів, staged оновлення, базові тестування продуктивності і стандартний runbook для «спайку трафіку». Ніякої героїки — тільки звички.
В день події CPU зріс, як очікувалося. Потім ще більше. Вони не панікували. Runbook починався з: визначити гарячу кінцеву точку, топ IP, чергу PHP-FPM, повільні запити MySQL. За кілька хвилин знайшли сплеск запитів до пошукової кінцевої точки, що обходила кеш сторінки. Це був реальний трафік, не боти.
Бо у них були заздалегідь підготовлені пом’якшення, вони увімкнули кешування результатів пошуку (короткий TTL), тимчасово зменшили складність пошуку (менше полів) і встановили розумний rate limit для підозрілих патернів. Сайт залишився в доступі. Ніхто, окрім тих, хто дивився графіки, не помітив проблеми.
Те, що врятувало день, не було модним інструментом. Це була наявність інструментації і передбачуваного процесу до кризи. «Нудно» — так називають надійність, коли вона працює.
Типові «гарячі точки» CPU у WordPress (хто зазвичай винен)
1) wp-login.php і credential stuffing
Високий CPU і багато POST на wp-login.php майже завжди означає brute force або credential stuffing. Навіть невдалі входи коштують CPU: хешування паролів, обробка сесій і хуки плагінів, що виконуються при спробах автентифікації.
Що робити: вводьте обмеження швидкості, додавайте bot-challenges на краю, забороняйте перелік користувачів і подумайте про переміщення адмінки за VPN або allowlist IP, якщо організація може це витримати.
2) xmlrpc.php (pingback + brute force)
XML-RPC можна зловживати як для спроб входу, так і для pingback-амліфікації. Багато сайтів не потребують його. Вимкнення часто радикально зменшує площу атаки.
3) admin-ajax.php (пулінг і фрагменти)
Admin AJAX — №1 джерело CPU від плагінів. Фронтенд-скрипти роблять пулінг (віджети чату, page builders, аналітика), WooCommerce оновлює fragments, і деякі плагіни використовують admin-ajax як свій API, бо він є.
Ознака: величезний rate запитів, постійний endpoint, багато IP і slowlog показує функції плагіна.
4) REST API кінцеві точки з необмеженими запитами
REST може бути шпигунськи навантажувальним для скреперів або вашого фронтенда, якщо ви пішли headless. Якщо ви дозволяєте великі per_page, дорогі фільтри або глибокі meta-запити — ви побудували CPU-шредер з приємним інтерфейсом.
5) Пошук і фільтрація (LIKE-запити, meta-запити)
Пошук WordPress відомо дорогий на великих наборах даних, особливо з WooCommerce і кастомними полями. Meta-запити на неіндексованих таблицях — повільна катастрофа.
6) Шторм wp-cron
Cron, що тригериться трафіком, означає, що сплески можуть спричинити більше запусків cron, що створює петлю зворотного зв’язку. У неї ввічлива назва — але це петля.
7) «Плагіни безпеки», що перевіряють кожен запит
Деякі плагіни безпеки детально інспектують кожен запит (regex, перевірки файлової системи, перевірки репутації IP). Вони можуть бути корисними, але також стати вашим найбільшим споживачем CPU. Вимірюйте їх як будь-який інший плагін.
Пом’якшення, що працюють (і ті, що потім кусаються)
Блокуйте і обмежуйте на краю спочатку
Якщо в історії присутній ворожий трафік — не «фіксуйте це в PHP». PHP — не той рівень для об’ємної атаки. Використовуйте WAF/CDN, Nginx rate limiting або правила фаєрвола.
Приклади обмеження швидкості в Nginx
Якщо ви керуєте Nginx, можна придушити агресивні кінцеві точки. Ціль — не карати легітимних користувачів. Мета — не дозволити одному актору зайняти всі воркери.
cr0x@server:~$ sudo nginx -T | grep -n "limit_req_zone" | head
53:limit_req_zone $binary_remote_addr zone=wp_login:10m rate=5r/m;
54:limit_req_zone $binary_remote_addr zone=wp_ajax:10m rate=30r/m;
Значення: Визначає пер-IP швидкості запитів. Логін має бути повільним; AJAX може бути вищим, але не нескінченним.
Рішення: Застосуйте це у відповідних location, потім перезавантажте Nginx і стежте за рівнем 429.
cr0x@server:~$ sudo grep -n "location = /wp-login.php" -n /etc/nginx/sites-enabled/default
121:location = /wp-login.php {
122: limit_req zone=wp_login burst=10 nodelay;
123: include snippets/fastcgi-php.conf;
124: fastcgi_pass unix:/run/php/php8.2-fpm.sock;
125:}
Значення: Спроби входу обмежені. Burst дозволяє короткі сплески; тривале зловживання отримує 429.
Рішення: Якщо бачите скарги користувачів — трохи послабте burst, але зберігайте низький rate. Brute force терплячі.
Зробіть wp-cron передбачуваним
Вимкніть cron-тригер від трафіку і запускайте його через справжній cron. Це одна з тих змін, що зменшує «таємничі» стрибки.
cr0x@server:~$ sudo -u www-data wp config set DISABLE_WP_CRON true --raw
Success: Updated the constant 'DISABLE_WP_CRON' in the 'wp-config.php' file.
Значення: WordPress припиняє запускати cron під час завантажень сторінок.
Рішення: Додайте системний cron, щоб запускати wp-cron.php з розумним інтервалом.
cr0x@server:~$ sudo crontab -u www-data -l | tail -n 3
*/5 * * * * cd /var/www/html && wp cron event run --due-now >/dev/null 2>&1
Значення: Cron тепер рухається за часом, а не трафіком.
Рішення: Якщо у вас багато бекґраунд-робіт (дії WooCommerce), підкоригуйте інтервал або використайте Action Scheduler більш тонко.
Кешуйте, серйозно (але не обманюйте себе)
Повноскринний кеш добре підходить для анонімного контенту. Об’єктний кеш корисний для повторюваних запитів до БД. Жоден з них не робить чищення кошика WooCommerce дешевим. Вони зменшують повторну роботу, щоб CPU витрачався на реальні дії користувачів, а не на ті ж самі запити.
Остерігайтеся «кешуй все» плагінів, що додають шари без телеметрії. Якщо ви не можете виміряти hit rate і модель евікцій, ви керуєте чутками.
Підійміть PHP-FPM по розміру (не ставте «безкінечно»)
Стрибки CPU часто починаються як повільні запити, потім перетворюються на чергу. Якщо pm.max_children занадто низький — ви чергуєте. Якщо занадто високий — витрачаєте всю RAM і свопитесь. Є золота середина, що залежить від середнього використання пам’яті воркером.
cr0x@server:~$ ps --no-headers -o rss -C php-fpm8.2 | awk '{sum+=$1; n++} END{print "avg_rss_kb=" int(sum/n) ", workers=" n}'
avg_rss_kb=89234, workers=24
Значення: Середній RSS воркера ≈87MB. Множте на max children і додайте накладні витрати для MySQL, OS cache і Nginx.
Рішення: Якщо у вас 2GB RAM, 24 воркери по 87MB це ≈2.1GB — замало без свопу. Обмежте кількість дитячих процесів і зменшуйте споживання пам’яті на запит.
Поширені помилки: симптом → корінь → виправлення
Цей розділ допоможе розпізнати власний інцидент. Це нормально. Ми всі через це проходили. Різниця — чи ви записуєте і припиняєте повторювати помилку.
1) Симптом: CPU 100%, великий load average; сайт таймаутиться
Корінь: PHP-FPM listen queue росте, бо запити повільні, а не тому, що потрібно більше воркерів.
Виправлення: Увімкніть PHP-FPM slowlog і атрибутуйте повільні шляхи; блокуйте/обмежуйте шкідливі кінцеві точки; зменшуйте роботу на запит; потім налаштуйте max children за RAM.
2) Симптом: CPU високий після увімкнення плагіна кешу
Корінь: Нагрів кешу або попереднє прогрівання, що б’є по кожному URL; або cache misses спричиняють stampede під високою конкуренцією.
Виправлення: Вимкніть агресивне warmup у продакшні; додайте блокування кешу, якщо підтримується; зменшіть конкуренцію воркерів для warmers; переконайтеся, що об’єктний кеш налаштований правильно.
3) Симптом: сплески кожні кілька хвилин як по годинах
Корінь: WP-Cron або Action Scheduler запускають важкі задачі, тригерені трафіком або неправильно розкладом.
Виправлення: Вимкніть WP-Cron і запустіть реальний cron; перегляньте заплановані події; виправте задачі, що роблять занадто багато за один прогін.
4) Симптом: багато admin-ajax запитів; CPU тане при «звичайному» трафіку
Корінь: Оновлення фрагментів WooCommerce, редактор page builder, або фронтенд-пулінг (heartbeat, чат), що генерують високу частоту некешованих викликів.
Виправлення: Зменште частоту; вимкніть фрагменти там, де безпечно; кешуйте відповіді; переконайтеся, що ці кінцеві точки не доступні анонімним користувачам без потреби.
5) Симптом: MySQL CPU високий; PHP помірний; запити показують «Sending data»
Корінь: Неіндексовані запити (meta-запити, LIKE-пошук), великі таблиці або дорогі сорти.
Виправлення: Увімкніть slow query log; знайдіть джерело запитів; додайте індекси обережно; змініть налаштування плагіна, щоб уникнути гірших запитів; додайте об’єктний кеш.
6) Симптом: CPU високий тільки під час сканів; сторінки здебільшого кешовані
Корінь: Боти обходять кеш через query string, заголовки або б’ють по некешованих кінцевих точках як sitemap та фіди.
Виправлення: Кешуйте sitemap; встановіть правила кешування для типових патернів ботів; обмежуйте швидкість; блокуйте аб’юзні юзер‑агенти, що ігнорують robots.
7) Симптом: випадкові 502/504 з’являються під час стрибків CPU
Корінь: Таймаути upstream (Nginx/Apache чекає на PHP-FPM), недостатня кількість воркерів або воркери зависли на повільних БД/віддалених викликах.
Виправлення: Корелюйте логи помилок з PHP-FPM slowlog; підвищуйте timeouts лише після скорочення часу запитів; уникайте маскування повільного бекенду постійним підняттям таймаутів.
Контрольні списки / покроковий план
Покроково: знайти бота або плагін, що навантажує, за 30–60 хвилин
- Підтвердіть, що CPU реальний: перевірте
mpstatна предмет steal і iowait. - Визначте найгарячіший процес:
psвідсортований за CPU. Здебільшого PHP-FPM. - Перевірте чергу PHP: сторінка статусу FPM; підтвердіть зростання черги.
- Знайдіть топ кінцеві точки: парсинг access логів для топ шляхів URL.
- Знайдіть топ клієнтів: топ IP для гарячих кінцевих точок. Рішення: блок/обмеження зараз.
- Увімкніть PHP-FPM slowlog (коротке вікно). Захопіть стек-трейси.
- Зіставте slowlog з плагіном/темою: шляхи файлів під
wp-content/pluginsабо директоріями тем. - Підтвердіть контролюваним тестом: тимчасово деактивуйте плагін через WP-CLI або feature flag; стежте за CPU і затримкою.
- Якщо підозрюється БД: перевірте processlist і slow query log; зіставте запити з кінцевими точками.
- Застосуйте довготривале рішення: заміна/налаштування плагіна, кешування, rate limits, зміни cron, оптимізація запитів.
- Напишіть короткий runbook: яка кінцева точка, який плагін, яке пом’якшення, яка метрика підтверджує відновлення.
Контрольний список: безпечні пом’якшення під час інциденту
- Обмежуйте
wp-login.php,xmlrpc.phpта агресивні/wp-json/маршрути. - Блокуйте очевидно погані IP (але віддавайте перевагу rate limits перед постійними блокуваннями).
- Увімкніть PHP-FPM slowlog тимчасово; збирайте докази.
- Масштабуйте вертикально лише якщо %steal низький і у вас є докази, що саме CPU — вузьке місце.
- Обмежте PHP-FPM workers, щоб уникнути смерті у swap.
- Вимкніть WP-Cron і запустіть реальний cron.
- Вимкніть конкретний плагін, що показаний у slowlog, якщо сайт потерпить його відсутність.
- Вимкніть дорогі неважливі фічі (related products, live search, fancy filters) на час інциденту.
Контрольний список: зміни після інциденту
- Збережіть базову метрику: request rate, p95 latency, черга PHP-FPM, топ кінцеві точки.
- Реалізуйте структуровані логи доступу, включаючи upstream time.
- Тримайте slow query log доступним (навіть якщо зазвичай вимкнений) і знайте, як швидко його увімкнути.
- Встановіть «бюджет продуктивності для плагінів»: уникайте плагінів, що роблять важку роботу на кожному запиті.
- Налагодьте графік оновлень з тестуванням на staging і планом відкату.
Питання й відповіді
1) Як зрозуміти, це боти чи реальні користувачі?
Шукайте концентрацію по IP, user agent та поведінці. Боти часто б’ють одну й ту ж кінцеву точку з високою частотою, ігнорують cookies і мають низьку різноманітність сторінок. Реальні користувачі переглядають сайт.
2) Чи варто просто додати більше ядер CPU?
Лише після атрибуції роботи. Масштабування може купити час, але також збільшить рахунок і посилить вузькі місця в БД. Якщо одна кінцева точка зловмисна — блокуйте її спочатку.
3) Чи завжди admin-ajax.php — погано?
Ні. Це легітимний механізм. Він стає поганим, коли використовується як високочастотний API для анонімних користувачів або коли відповіді тригерять дорогі запити до БД.
4) Який найшвидший спосіб знайти плагін, що викликає навантаження?
PHP-FPM slowlog зі стек-трейсами — найшвидший надійний метод. Журнали доступу кажуть, що гаряче; slowlog каже, повільний.
5) Мій CPU високий, але PHP-FPM ні. Що тоді?
Перевірте CPU MySQL і processlist. Також перевірте i/o wait і використання swap. Іноді це індексування пошуку, обробка зображень, backup jobs або інший орендар на тому ж хості.
6) Чи кеш може вирішити сплески CPU WooCommerce?
Частково. Можна кешувати анонімні сторінки і деякі фрагменти, але кошик/чек-аут і залоговані дії залишаться динамічними. Зосередьтеся на зменшенні chatter через admin-ajax і дорогих запитів.
7) Чи варто вимикати xmlrpc.php?
Якщо ви ним не користуєтесь (більшість сайтів не користуються) — так, вимкніть або заблокуйте. Якщо треба зберегти — сильно обмежте швидкість і моніторьте спроби входу.
8) Чи Redis об’єктний кеш завжди корисний?
Він корисний, коли знижує повторні звернення до БД і кеш стабільний. Він може нашкодити, якщо маскує неефективний код до моменту евікції або якщо помилкова конфігурація спричиняє постійні misses.
9) Чому сплески CPU корелюють з cron?
Тому що WP-Cron може тригеритися на завантаженнях сторінок, створюючи петлі зворотного зв’язку. Перенесення його в реальний cron перетворює несподівані стрибки на заплановану роботу.
10) Яка найпоширеніша причина WordPress 100% CPU?
Високий обсяг запитів до динамічної кінцевої точки, що обходить кеш, у поєднанні з повільним кодом плагіна або дорогими запитами до БД. Рідко це лише «ядро WordPress».
Висновок: наступні кроки, які можна зробити сьогодні
Якщо ваш WordPress сайт сідає на CPU, не ставте це як загадку. Ставте як розслідування з доказами.
- Пройдіть швидкий план діагностики: визначте процес → кінцеву точку → клієнта → шлях коду.
- Увімкніть PHP-FPM slowlog на короткий час і захопіть стек-трейси під час спайку.
- Проаналізуйте access логи щоб знайти топ URL і IP. Негайно обмежуйте зловмисні кінцеві точки.
- Зіставте стек-трейси з плагінами і деактивуйте/замініть винуватців контрольовано.
- Зробіть cron передбачуваним і правильно налаштуйте PHP-FPM, щоб уникнути черг і своп-трясіння.
- Запишіть, що ви дізналися: кінцева точка, плагін, пом’якшення і метрика, що підтверджує усунення. Майбутній ви буде вдячний.
CPU — це не моральний провал. Це рахунок, який ви платите в реальному часі. Отримайте чек: запит, плагін, запит до БД, клієнт. А потім припиніть це.