Кейс: оптимизация сервера
Пошаговый разбор реальной оптимизации: 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 минуты, без гадания на кофейной гуще и «кажется, это хостинг тупит».
# Топ процессов по 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
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 и прочая статика.
# Первый запрос — 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
# Самые тяжёлые функции из 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
# Список запланированных задач
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()'
# Сколько ботов заблокировано за сегодня
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 формально был готов помогать, но его никто не позвал.
Логика работы:
Ключевые условия, при которых кэш не применяется:
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
FastCGI Microcache nginx
Вывод: оба кэша дополняют друг друга. 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 и без покупки очередной «волшебной коробки» за деньги.