Метрика

Снижение нагрузки CPU с 400% до 30–80% на WordPress + WooCommerce: реальный кейс оптимизации сервера

Рубрики:VDS
Автор: Пряхин Станислав
Время прочтения: мин
Комментариев: нет
09.04.2026

Кейс: оптимизация сервера

Пошаговый разбор реальной оптимизации: FastCGI-кэш, блокировка ботов, PHP-FPM в режиме dynamic и системный cron через WP-CLI.

Стек: HestiaCP / nginx · PHP 8.4-FPM · WooCommerce
Период: апрель 2026

Показатель Значение
CPU до оптимизации 400%
CPU после 30–80%
Ботов заблокировано / день 16 000+
Статус FastCGI-кэша HIT

Ситуация: WooCommerce-магазин на PHP 8.4 под управлением HestiaCP регулярно уходил в пиковую нагрузку 300–400% CPU. Сайт тормозил, PHP-FPM не успевал обслуживать запросы. За один день были найдены и устранены основные причины перегруза.

Диагностика: с чего начать

Перед любыми изменениями нужно понять, что именно создаёт нагрузку. Эти команды дают нормальную картину за 2–3 минуты, без гадания на кофейной гуще и «кажется, это хостинг тупит».

1
CPU и память
# Топ процессов по CPU
ps aux --sort=-%cpu | head -20

# PHP-FPM воркеры — сколько активны прямо сейчас
ps aux | grep "php-fpm: pool" | grep -v grep | wc -l

# Средняя нагрузка за 1 / 5 / 15 минут
uptime

# Свободная память
free -h
2
Кто долбит сервер
DOMAIN="krokodil.toys"
LOG="/var/log/nginx/domains/$DOMAIN.log"

# Топ IP по количеству запросов за сегодня
awk '{print $1}' $LOG | sort | uniq -c | sort -rn | head -20

# Топ User-Agent — видно ботов
grep -oP '"[^"]*"$' $LOG | sort | uniq -c | sort -rn | head -20

# Топ атакуемых URL
awk '{print $7}' $LOG | sort | uniq -c | sort -rn | head -20

На что смотреть: если один IP делает 1000+ запросов — это уже не «интерес к сайту», а бот. Если один User-Agent повторяется десятки тысяч раз — перед тобой парсер. Типовые цели: wp-login.php, xmlrpc.php, /wp-admin/, SVG, JPG и прочая статика.

3
Проверка кэша
# Первый запрос — MISS, второй — HIT
curl -sI https://$DOMAIN/ | grep -i "x-fastcgi-cache"
curl -sI https://$DOMAIN/ | grep -i "x-fastcgi-cache"

# Проверка нескольких страниц
for url in "/" "/product-category/igrushki-i-igry/" "/product-category/koljaski/"; do
  result=$(curl -sI "https://$DOMAIN$url" | grep -i "x-fastcgi-cache")
  echo "$url → $result"
done
4
PHP slow log
# Самые тяжёлые функции из slow log
tail -200 /var/log/php8.4-fpm-krokodil-slow.log | \
  grep "function name" | sort | uniq -c | sort -rn | head -10

# Какие скрипты попадают в slow log чаще всего
tail -200 /var/log/php8.4-fpm-krokodil-slow.log | \
  grep "script_filename" | sort | uniq -c | sort -rn | head -10
5
Зависшие WP-Cron задачи
# Список запланированных задач
wp cron event list \
  --path=/home/krokodil.toys/web/krokodil.toys/public_html \
  --allow-root | head -20

# Просроченные задачи
wp cron event list \
  --path=/home/krokodil.toys/web/krokodil.toys/public_html \
  --allow-root | awk '$3 < systime()'
6
Статистика блокировок
# Сколько ботов заблокировано за сегодня
grep "$(date '+%d/%b/%Y')" $LOG | \
  awk '$9 == "444" || $9 == "403"' | \
  grep -oP '"[^"]*"$' | sort | uniq -c | sort -rn | head -15

Алгоритм диагностики: сначала смотрим slow log и активные процессы, потом access-log, затем кэш. В большинстве случаев 1–2 причины дают основную часть нагрузки. Всё остальное — уже косметика, а не лечение.

1. Диагностика: что жрёт CPU

Зависший cron-хук — главный виновник

Проверка slow log PHP-FPM и активных процессов показала зависший хук cron_update_products_single_image, который запускался при каждом cron-цикле и не завершался.

# Поиск зависших хуков
wp cron event list --path=/.../ --allow-root | grep update_products

# Удаление зависшего хука
wp cron event delete cron_update_products_single_image --allow-root
Результат

CPU сразу упал примерно на 50%. Хук крутил тяжёлую обработку изображений по кругу, как будто ему за это платили.

PHP-FPM в режиме ondemand не выдерживал пики

Режим ondemand создаёт новые PHP-процессы только по факту запроса. При всплеске трафика все воркеры быстро занимались, а новые запросы вставали в очередь.

Проблемная конфигурация: pm = ondemand, pm.max_children = 8, pm.process_idle_timeout = 10s.

Решение

Переключили пул в dynamic: часть воркеров постоянно живёт в памяти и готова принимать нагрузку без постоянного пересоздания процессов.

pm = dynamic
pm.max_children = 12
pm.start_servers = 3
pm.min_spare_servers = 2
pm.max_spare_servers = 5
pm.max_requests = 500

Почему max_requests = 500, а не 4000: это помогает регулярно перезапускать воркеры и не копить утечки памяти от плагинов. На живом WooCommerce это не паранойя, а гигиена.

2. FastCGI Microcache — PHP не должен просыпаться без причины

В /etc/nginx/nginx.conf уже была настроена зона microcache, но к сайту она не была подключена. То есть nginx формально был готов помогать, но его никто не позвал.

Логика работы:

GET-запрос приходит в nginx
nginx проверяет: есть ли готовый кэш по URL
Если HIT — страница отдаётся сразу, PHP не запускается
Если MISS — запрос уходит в PHP-FPM и результат кладётся в кэш
Корзина, checkout, wp-admin и авторизованные пользователи обходят кэш

Ключевые условия, при которых кэш не применяется:

set $no_cache 0;

if ($request_method = POST)         { set $no_cache 1; }
if ($query_string != "")            { set $no_cache 1; }
if ($request_uri ~* "(/wp-admin/|/cart|/checkout|/my-account)") {
    set $no_cache 1;
}
if ($http_cookie ~* "wordpress_logged_in|woocommerce_items_in_cart") {
    set $no_cache 1;
}

Подключение FastCGI-кэша в nginx.ssl.conf_custom:

fastcgi_cache microcache;
fastcgi_cache_valid 200 301 302 30m;
fastcgi_cache_valid 404 2m;
fastcgi_cache_bypass $no_cache;
fastcgi_no_cache $no_cache;
fastcgi_cache_use_stale error timeout updating invalid_header http_500 http_503;
fastcgi_cache_lock on;
add_header X-FastCGI-Cache $upstream_cache_status;
# Проверка: второй запрос должен вернуть HIT
curl -sI https://krokodil.toys/product-category/igrushki-i-igry/ | grep x-fastcgi

Результат: страницы каталога и товаров стали отдаваться из памяти nginx без запуска PHP. Время ответа сократилось примерно с 800–1200 мс до 15–40 мс.

3. Блокировка ботов — 16 000+ лишних запросов в день

Access-log показал, что значимую часть нагрузки создавали боты-парсеры: Bytespider, GeedoShopProductFinder, SemrushBot, MegaIndex и другие. Они по сотне раз качали одни и те же файлы, как будто искали в SVG смысл жизни.

Бот Запросов / день После блокировки
Bytespider (ByteDance) 16 214 444
meta-externalagent 879 444
MegaIndex 486 444
SemrushBot 154 444
PetalBot 127 444
GeedoShopProductFinder 1 618 444

Блокировка вынесена в /etc/nginx/conf.d/block_bots.conf через map и geo:

map $http_user_agent $bad_bot {
    default 0;
    ~*Bytespider             1;
    ~*GPTBot                 1;
    ~*GeedoShopProductFinder 1;
    ~*AhrefsBot              1;
    ~*SemrushBot             1;
    ~*MegaIndex              1;
    ~*DataForSeoBot          1;
    ~*ClaudeBot              1;
    ~*Amazonbot              1;
}

geo $bad_ip {
    default 0;
    47.128.98.0/24  1;
    47.128.99.0/24  1;
    43.173.0.0/16   1;
}

Подключение блокировки в конфиге сайта:

if ($bad_bot) { return 444; }
if ($bad_ip)  { return 444; }

Почему 444, а не 403: код 444 в nginx просто закрывает соединение без ответа. Бот не получает ни страницы, ни заголовков, ни полезной информации. Меньше трафика, меньше болтовни, меньше шансов, что он решит «попробовать ещё».

4. WP-Cron через WP-CLI вместо HTTP

По умолчанию WordPress пытается запускать wp-cron.php при заходах пользователей. При нагрузке это выливается в бессмысленные параллельные старты cron через веб. Для продакшена это плохая привычка, от которой пора взрослеть.

Параметр HTTP cron WP-CLI cron
Проходит через nginx Да Нет
Работает при проблемах сайта Нет Да
Частота запуска Неконтролируемая По cron
Нагрузка Высокая Минимальная
# crontab -e
*/15 * * * * /usr/bin/wp cron event run --due-now \
  --path=/srv/jail/krokodil.toys/.../public_html/ \
  --allow-root > /dev/null 2>&1

Важно: в wp-config.php нужно добавить:

define('DISABLE_WP_CRON', true);

Иначе cron будет запускаться и через CLI, и через HTTP одновременно. А это уже не оптимизация, а цирк с двумя входами.

5. Два кэша: плагин + FastCGI — зачем оба

Частый вопрос: зачем FastCGI-кэш, если уже установлен плагин кэширования? Ответ простой: потому что у них разная работа.

Плагин кэша WordPress

Умеет сбрасывать кэш при изменении товара или записи
Лучше понимает WooCommerce-сценарии
PHP всё равно запускается для обработки логики

FastCGI Microcache nginx

При HIT PHP не запускается вообще
Резко ускоряет отдачу каталога и карточек
Инвалидация в базовом варианте работает по времени, а не по событию

Вывод: оба кэша дополняют друг друга. FastCGI снимает нагрузку с PHP, а плагин помогает правильно управлять сбросом кэша при изменениях контента.

6. Итоговые результаты

Проблема Решение Эффект
Зависший cron-хук Удалили через WP-CLI −50% CPU
PHP-FPM в режиме ondemand Переключили на dynamic −20% пиковой нагрузки
FastCGI-кэш не работал Подключили зону microcache −70% запросов к PHP
Боты без ограничений map + geo в nginx −15% внешних запросов
xmlrpc.php открыт Закрыли доступ Повышение безопасности
wp-login.php без rate limit Добавили limit_req Защита от brute-force

Итог: CPU с постоянных 200–400% стабилизировался на 30–80% в обычном режиме. Пики выше 150% больше не наблюдаются. Память осталась примерно на уровне 1 ГБ. Все изменения выполнены на уровне nginx и PHP-FPM — без смены хостинга, без CDN и без покупки очередной «волшебной коробки» за деньги.

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

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

Другие статьи

WebCreative Studio Logo

🍪 Мы собираем куки. Без них никак. Они нужны для вашего удобства — сайт просто не сможет нормально работать без них.

Создание сайтов в Донецке
Спасибо! Ваша заявка получена. Мы дадим обратную связь в ближайшее время.