Нагрузка на процессор 100% на WordPress: не атака, а краулеры

/ Разработка 
Nov 9, 2026 | 7 min read
Иллюстрация, созданная ИИ — A woman in a black dress stands in a concrete room, hand to her chin, thinking.
Изображение создано ИИ
Olga LescuyerFrontend Developer,SEO specialist

Наш сервер WordPress снова и снова упирался в лимит: нагрузка на процессор 100%. Причиной оказалась не атака, а наш собственный сайт. Краулеры заходили в основном на одну страницу, список статей блога. Сайт при каждом визите частично пересобирал её, и каждая пересборка отправляла в WordPress новые запросы.

Всё началось с письма от хостинга. Там видели около 2 400 запросов в день с нескольких IP-адресов американских облаков, с user agent node. Хостинг решил, что их шлёт какой-то скрипт или цикл опроса. Однажды вечером процессор 40 минут держался на 100%.

Ниже рассказываем, как мы нашли причину и что поменяли. В конце есть чек-лист для вашего headless WordPress.

Как устроен сайт: кто обращается к WordPress

Наш сайт headless. WordPress только хранит контент. Страницы, которые видят посетители, собирает приложение на Next.js на другом хостинге.

  • Сайт получает контент через WPGraphQL на трёх языках (EN, FR, RU).
  • Посетители и боты никогда не обращаются к WordPress напрямую. Страницы им отдаёт сервер, и большинство из них берёт из кэша.

Последний пункт важен. Когда сервер пересобирает страницу, он снова запрашивает данные у WordPress. Значит, каждый просмотр страницы, которой нет в кэше, создаёт работу для WordPress.

Два кэша перед базой данных. Публикация очищает оба.

Список статей пересобирался при каждом визите, потому что пагинация работала через параметры URL. Кэш был с самого начала, но Next.js кэшировал только сами страницы, а не их варианты с параметрами. Поэтому каждый заход краулера на эту страницу нагружал WordPress.

Что показали логи

Первой подсказкой стал user agent node. Его отправляет Next.js, когда получает данные на сервере. Американские облачные IP совпадали с регионом по умолчанию нашей хостинг-платформы. Значит, запросы слало наше же приложение. Оставался вопрос: почему так часто?

Ответ дали логи запросов хостинг-платформы. Мы выгрузили 23 часа логов через её консольную утилиту. Потом посчитали страницы, которые платформа пересобирала, вместо того чтобы отдать из кэша.

СтраницаПересборок за 23 чКто заходил чаще всего
/blog (все 3 языка)1 344ClaudeBot (1 073), затем MJ12bot, PetalBot и другие
/worksоколо 90разные
Всё остальноепочти всегда из кэша—

Логи ошибок и история деплоев дали ещё две находки:

  • Сканеры запрашивали пути вроде /.env и /ads.txt. Главная страница принимала .env за код языка и отправляла с ним запрос в WordPress. В ответ получался 404.
  • Сборки совпали с самым тяжёлым вечером. Хостинг-платформа запустила шесть полных сборок за 40 минут. Каждая сборка запрашивала все страницы на трёх языках.

Ещё одну подсказку дала история хостинга за 30 дней. В два самых тяжёлых дня мы почти ничего не деплоили. Сборки давали короткие пики, а постоянную нагрузку создавали краулеры.

Почему нагрузка на процессор доходила до 100%

Одного конкретного бага не было. Сложились четыре вполне обычных решения.

  1. Одна страница никогда не кэшировалась. Список статей читает из URL ?page= и ?tag=. Из-за этого Next.js рендерит страницу заново на каждый запрос. Краулер, который перебирает все сочетания страниц и тегов, пересобирает её много раз. А каждая пересборка ещё и пересчитывала все статьи, по шесть за раз, только чтобы показать номера страниц.
  2. Данные устаревали каждый час. Все запросы к WordPress мы кэшировали на час. Между двумя визитами краулера кэш часто успевал истечь, и пересборка снова шла в WordPress.
  3. Каждый релиз собирал сайт трижды. Хостинг-платформа собирала полную копию на каждом этапе нашего процесса проверки.
  4. WordPress не кэшировал запросы. Плагин WPGraphQL Smart Cache у нас стоял. Но его объектный кэш (object cache), та часть, что работает с нашими POST-запросами, был выключен. Поэтому каждый запрос целиком проходил через PHP и базу данных.

Пункты 1 и 4 появились не вчера: они существовали больше года. Изменился трафик. Краулеры нейросетей теперь приходят гораздо чаще, а наш robots.txt их пускал.

Что мы поменяли

Мы внесли пять небольших изменений. Ни одно не потребовало новой инфраструктуры.

ИзменениеГдеЧто даёт
Запрет ?page= и ?tag= в robots.txt, Crawl-delay: 10 для ботовNext.jsКраулеры не заходят на некэшируемые варианты URL. Поисковики по-прежнему находят статьи через sitemap.xml
Кэш данных WordPress на 24 часа вместо 1 часаNext.jsКаждая страница берёт свежие данные из WordPress не чаще раза в день
404 для неизвестных кодов языка до любого запросаNext.jsURL сканеров вроде /.env не доходят до WordPress
Собирать только продакшен, по одной сборке за разНастройки хостингаОдна сборка на релиз, а не три одновременно
Включить объектный кэш Smart Cache на 24 часаНастройки WordPressПовторные запросы, включая большинство запросов при сборке, не идут в базу данных

Первое изменение сработало за одну ночь. Пересборки списка статей от ClaudeBot упали примерно с 84 за два часа до нуля.

Два кэша перед базой данных. Публикация очищает оба.

Запрос доходит до базы данных, только если промахнулись оба кэша. Публикация очищает оба кэша, поэтому новый контент появляется через несколько секунд.

Результаты: что стало с нагрузкой на сервер

Через пять дней WordPress получал 176 GraphQL-запросов в день. Это больше чем на 90% меньше, чем раньше, и сервер ни разу не упёрся в лимит CPU.

ПоказательДо4 ноября5 ноября6 ноября7 ноября8 ноября
GraphQL-запросы к WordPress2 400–2 900—1 196334335176
Превышения лимита CPUв большинство днейнесколькобывалинескольконесколькони одного
Продакшен-сборки3 на релиз25010
Пересборки списка статейоколо 1 30026843815942
Визиты ClaudeBot на список статей1 0734—000

Каждый столбец — это 24 часа примерно с 09:00 по парижскому времени. Прочерк значит, что данных за этот день у нас нет. 6 ноября больше половины запросов создала наша собственная разработка.

Оставшиеся превышения шли короткими всплесками. Каждая продакшен-сборка запрашивает весь сайт сразу. В дни, когда мы пишем код, наша разработка добавляет запросов. Последним исправлением стала настройка WordPress: объектный кэш Smart Cache. Он хранит ответы достаточно долго, чтобы покрыть время между двумя релизами. 8 ноября релиза не было, так что результат покажет следующая сборка.

Следующий шаг: список статей (ещё не доделали)

Эта часть ещё в работе. Список статей по-прежнему ссылается на свои же варианты: в HTML страницы /blog 11 ссылок вроде ?page=2 и ?tag=seo. Краулеры переходят по каждой, и одна страница превращается в десятки URL. Sitemap от этого не спасает, потому что краулеры сначала идут по ссылкам. Правило в robots.txt останавливает вежливых краулеров, но причину не устраняет.

Мы публикуем около двух статей в неделю, то есть за год на каждом языке наберётся около 100 статей. Решение должно выдерживать такой рост. Сравниваем три варианта:

ВариантКак работаетПлюсыМинусы
Все статьи на одной страницеНа странице списка все статьи. Номера страниц и теги фильтруются в браузереПросто. Одна статическая страница. Нет вариантов URLПри 100 и более статьях страница становится тяжёлой
Страницы в пути URL/blog/page/2, /blog/tag/seo. Каждая страница статическая и содержит только свои статьиВыдерживает сотни статей. Настоящие URL, которые поисковики могут индексироватьБольше маршрутов. Для старых ссылок ?page= нужны редиректы
Частичный рендеринг (Next.js 16)Статическая оболочка страницы. Блок со списком кэшируется для каждой страницы и тегаНебольшое изменение в коде страницыНужно крупное обновление Next.js. Краулеры по-прежнему ходят по каждой ссылке ?page=

Пока ни один вариант нам полностью не подходит: каждый решает часть проблемы и добавляет новые издержки. Так что ищем дальше и допишем сюда результат.

Три слоя кэша, от визита до базы данных. База работает только после публикации.

Что бы мы ни выбрали, цель та же: обычный визит останавливается на кэше страниц, а база данных работает только после публикации. Публикация очищает все три кэша, поэтому новые статьи по-прежнему появляются через несколько секунд.

Чек-лист: что проверить, если headless WordPress грузит сервер

Хостинг жалуется на большой GraphQL-трафик к WordPress? Проверьте эти пункты, прежде чем переходить на тариф подороже.

  1. Выясните, кто шлёт запросы. User agent node означает ваш собственный фронтенд. Имя бота значит, что бот обращается к WordPress напрямую.
  2. Найдите страницы, которые никогда не кэшируются. В логах хостинга ищите маршруты, где всегда стоит MISS. Обычно это страницы, которые читают параметры из URL.
  3. Посмотрите, кто заходит на эти страницы. Если в основном краулеры, закройте варианты URL в robots.txt. Саму страницу целиком не закрывайте.
  4. Храните кэш дольше. Кэшируйте данные WordPress на сутки. Обновляйте их по вебхуку при публикации. Не запрашивайте свежие данные каждый час.
  5. Посчитайте сборки. Каждая превью-сборка запрашивает весь сайт. Если превью вам не нужны, собирайте только продакшен.
  6. Включите объектный кэш. В WPGraphQL Smart Cache отметьте Use Object Cache. Поменяйте срок хранения с 10 минут по умолчанию на 24 часа или больше. Этот кэш работает с POST-запросами и сам очищается при публикации.
  7. Отсекайте мусорные URL сразу. До запроса к WordPress отдавайте 404 на любой URL, которого нет среди настоящих маршрутов.
  8. Контролируйте URL фильтров и страниц. Дайте страницам и фильтрам настоящие кэшируемые URL или уберите их из ссылок. Иначе одна страница списка превращается для краулеров в десятки URL.

Европейская студия с русскоязычной командой: если сайт тормозит или хостинг обходится слишком дорого, разберём ваши логи вместе

Связаться с нами