01Итоги в цифрах
Типичные сутки до начала работ (в среднем за 4–13 октября) против 20 октября, первых полных суток после всех изменений.
Посещаемость из поисковых систем не изменилась: около 1 000 уникальных посетителей и 2,3 тыс. переходов в сутки, как и до работ. Защита отсекает ботов, а не людей.
02Что было
Проблему нашли 14 октября: сервер с 4 ядрами работал при нагрузке 16–17, то есть очередь задач в четыре раза превышала его возможности. Анализ журналов показал, откуда она берётся.
| Источник | Что происходило | Запросов в сутки |
|---|---|---|
| Ботнет из ~26 тыс. адресов | Без реферера, с поддельными браузерами Chrome. Обходил ссылки вида /catalog/cart/<хэш>/: каждый просмотр корзины порождал новый уникальный адрес, и обход никогда не заканчивался | 100–265 тыс. |
| Мусорные адреса | Боты переходили по ссылкам tel: и mailto: как по страницам, и каждая такая «страница» запускала движок сайта | 5–40 тыс. |
| SEO- и ИИ-краулеры | Semrush, Ahrefs, Baidu, Bytespider, GPTBot и др. обходили весь сайт без ограничений | десятки тыс. |
| Сборщик превью обходил главные страницы всех региональных поддоменов | ≈ 20 тыс. | |
| Служебные запросы | Каждая страница отдельно запрашивала языковой словарь и город по IP: в сутки это почти 90 тыс. дополнительных обращений к движку | ≈ 85 тыс. |
Дополнительно: на сервере был включён режим отладки. В этом режиме кэш маршрутов пересоздавался при каждом запросе, а через отладочный адрес можно было выгрузить базу данных и код сайта.
03Как устроена защита
Перед сайтом стоит веб-сервер nginx. Он отвечает на запрос за доли миллисекунды и почти не тратит ресурсы. Движок сайта (PHP и база данных) на каждый запрос тратит в сотни раз больше. Главная идея — чтобы ботам отвечал nginx, а до движка доходили только нужные запросы.
Отказ — 403
SEO-краулеры, которые не приносят посетителей: Semrush, Ahrefs, DotBot, Baidu, Exa и др.
Ограничение — 429
ИИ-боты и Facebook могут работать, но не чаще заданного темпа. Лишние запросы получают «повторите позже».
Статический ответ
Пустая корзина без товаров отдаётся готовой страницей в 418 байт вместо страницы в 87 КБ, собранной движком.
Редирект и 404
Старые адреса корзины с хэшем ведут на единый адрес, а мусорные tel:/mailto: получают 404.
04Что сделано
Работы шли в три этапа. После каждого этапа мы проверяли журналы и по ним выбирали следующий шаг.
-
14 октябряэтап 1
- Выключен режим отладки на рабочем сервере.
- Добавлены правила для ботов в nginx: SEO-краулерам отказ, ИИ-ботам и Facebook ограничение частоты.
-
15 октябряэтап 2
- Корзина получила постоянный адрес
/catalog/cart/вместо уникального на каждый просмотр. Старые адреса перенаправляются. #155 - Удалён отладочный адрес выгрузки базы и кода. #154
- Меню строится без отдельного запроса к базе на каждый пункт. #153
- Языковой словарь загружается один раз и кэшируется в браузере. #156
- Город по IP определяется фоновым запросом, а не при сборке каждой страницы. #157
- Страницы стали легче: иконка Rutube уменьшена с 205 до 5 КБ, удалён неиспользуемый скрипт. #158
- Пересжаты изображения статей, новостей и баннеров: 167 → 75 МБ без видимой потери качества.
- Мусорные адреса
tel:иmailto:отсекаются на уровне nginx.
- Корзина получила постоянный адрес
-
19 октябряэтап 3
- Ботнет переключился на сам адрес
/catalog/cart/(60–100 тыс. запросов в сутки). Теперь посетителю без товаров в корзине nginx отдаёт готовую страницу «Корзина пуста», и движок не запускается. - Baidu и ExaSearchBot полностью запрещены. Для Facebook введён отдельный лимит — 6 запросов в минуту.
- Включён журнал запросов, отклонённых nginx, чтобы видеть объём отсечённого трафика.
- Исправлены предупреждения в модуле региональных редиректов: они давали около 36 тыс. строк в сутки в журнале ошибок. #159
- Ботнет переключился на сам адрес
Номера с решёткой — заявки на изменение (pull request) в репозитории сайта. Перед каждым изменением на сервере сохранялась резервная копия.
05Результаты
Данные из журналов сервера, по суткам, с 4 по 20 октября. Вертикальные линии — даты трёх этапов работ. Наведите курсор на столбец, чтобы увидеть значение.
Запросы, дошедшие до движка сайта
тыс. запросов в сутки, без картинок, стилей и скриптов
Пики 11–12 и 15 октября — волны ботнета по адресам корзины. После этапа 2 (15 октября) ботнет переключился на постоянный адрес корзины, поэтому 16–18 октября нагрузка оставалась высокой. Этап 3 закрыл и этот путь: 20 октября до движка дошло 54 тыс. запросов против обычных 250–300 тыс.
Записи в журнале ошибок
тыс. строк в сутки
Посетители из поисковых систем
уникальные IP с переходом из Яндекса, Google и др.
Журнал ошибок почти пуст: 793 строки против обычных 20–30 тыс. Это ускоряет работу и облегчает поиск настоящих неполадок. Всплеск 15–16 октября вызвали предупреждения модуля региональных редиректов, они исправлены 19 октября. Число посетителей из поиска при этом стабильно на протяжении всего периода.
Что nginx отсёк сам, 20 октября
106 тыс. запросов в сутки, движок сайта на них не запускался
Ботнет продолжает приходить: около 82 тыс. запросов в сутки к адресам корзины. Теперь это для сайта почти бесплатно — nginx отвечает на них сразу. Для сравнения: до работ каждый такой запрос собирал страницу в 87 КБ и нагружал базу данных.
Сравнение по суткам
| Показатель | До работ 4–13 окт., среднее | После этапа 2 16–18 окт. | После этапа 3 20 окт. | Изменение |
|---|---|---|---|---|
| Запросы к движку сайта | ≈ 286 тыс. | ≈ 338 тыс. | 54 тыс. | −81% |
| Трафик страниц (без статики) | ≈ 21 ГБ | ≈ 20 ГБ | 4 ГБ | −81% |
| Служебные POST-запросы | ≈ 84 тыс. | ≈ 136 тыс. | 15 тыс. | −82% |
| Запросы ботнета, дошедшие до движка | ≈ 32 тыс. | ≈ 18 тыс. | 838 | −97% |
| Строки в журнале ошибок | ≈ 38 тыс. | ≈ 73 тыс. | 793 | −98% |
| Нагрузка на сервер (load average) | 16–17 | 1–2 | 0,2–0,8 | в 20+ раз |
| Посетители из поиска (уникальные IP) | ≈ 1 050 | ≈ 830 | 1 043 | без изменений |
Средние «до работ» включают волну ботнета 11–12 октября. Данные за 21 октября (неполные сутки, до 11:40) идут в том же темпе, что и за 20-е.
06Что дальше
Основная задача решена: сервер работает с большим запасом. Ниже — наблюдения, которые не требуют срочных действий.
| Наблюдение | Рекомендация |
|---|---|
| Ботнет продолжает обращаться к адресам корзины (≈ 82 тыс./сутки) | Ничего не делать: nginx отвечает за доли миллисекунды. Следить по журналу отклонённых запросов. |
Несколько источников с подозрительными подписями — Chrome/99 (5,3 тыс./сутки), GuzzleHttp (2,9 тыс.), Chrome/48 (1,8 тыс.) | Уточнить, нет ли среди них собственных интеграций (Guzzle — PHP-клиент, часто используется сервисами). Если нет, можно ограничить. |
| Около 750 предупреждений в сутки от модуля Roistat | Не влияют на работу сайта. Модуль не трогаем до решения по договору с Roistat. |
| Устаревшая платформа сервера (CentOS 7, PHP 5.6) | Сейчас ресурсов хватает с запасом. При следующем росте нагрузки в первую очередь стоит обновить платформу. |