Недавно ко мне обратился человек с вопросом по логам сайта на WordPress. Ситуация классическая: открываешь access.log в Nginx, а там запросы к /wp-login.php, /xmlrpc.php, странные обращения к плагинам, попытки открыть .env, какие-то неизвестные IP и ощущение, что сайт уже штурмуют как крепость.
Паниковать в такой ситуации не надо. Но и махать рукой тоже нельзя. Почти любой WordPress-сайт, который виден из интернета, рано или поздно начинает собирать такой фоновый мусор. Боты сканируют всё подряд: маленькие сайты услуг, блоги, магазины, лендинги, старые домены, новые домены. Им всё равно, что у вас там: юридическая компания, сайт стоматолога или блог про Linux и WordPress.
Поэтому в этой статье разберу, как смотреть такие логи, что в них реально важно, почему запросы к wp-login.php и xmlrpc.php появляются почти у всех, и как я обычно выстраиваю защиту: смена адреса входа, CAPTCHA, двухфакторная авторизация, ограничения в Nginx, Fail2ban и Nginx Ultimate Bad Bot Blocker.
Что было в логе и почему это выглядит страшно
Когда человек впервые смотрит в access.log, он обычно видит не красивую картину работы сайта, а поток технической каши. Особенно если сайт уже какое-то время существует и проиндексирован поисковиками.
Типичная обезличенная картина выглядит примерно так. IP-адреса и домен я заменил на безопасные примеры, реальные данные светить незачем:
203.0.113.10 - - [26/May/2026:09:14:22 +0300] "GET /wp-login.php HTTP/1.1" 200 5123 "-" "Mozilla/5.0" 198.51.100.24 - - [26/May/2026:09:14:25 +0300] "POST /wp-login.php HTTP/1.1" 200 6210 "https://example.com/wp-login.php" "Mozilla/5.0" 192.0.2.55 - - [26/May/2026:09:15:03 +0300] "POST /xmlrpc.php HTTP/1.1" 200 403 "-" "Mozilla/5.0" 198.51.100.88 - - [26/May/2026:09:16:41 +0300] "GET /wp-content/plugins/old-plugin/readme.txt HTTP/1.1" 404 162 "-" "python-requests/2.31" 203.0.113.77 - - [26/May/2026:09:17:08 +0300] "GET /.env HTTP/1.1" 404 162 "-" "curl/8.1.2" 192.0.2.91 - - [26/May/2026:09:17:33 +0300] "GET /wp-admin/install.php HTTP/1.1" 403 146 "-" "Mozilla/5.0"
Если смотреть на это без опыта, кажется, что сайт прямо сейчас ломают. На самом деле тут видны обычные автоматические проверки: страница входа WordPress, XML-RPC, старые плагины, служебный файл .env, попытка открыть установщик WordPress.
Сам факт таких запросов ещё не означает взлом. Это означает, что сайт попал в поле зрения ботов. В интернете это почти неизбежно. Вопрос не в том, будут ли такие запросы. Будут. Вопрос в том, насколько спокойно сайт их переживает.
Это у всех WordPress-сайтов такое?
Да, у большинства живых WordPress-сайтов в логах можно найти похожую активность. Особенно если домен не новый, сайт индексируется, на него есть ссылки или он когда-то попадал в разные базы сканеров.
Боты работают тупо, но массово. Они не изучают ваш бизнес, не читают страницу «О компании» и не думают, стоит ли вас тревожить. Они просто перебирают типовые адреса:
/wp-login.php/xmlrpc.php/wp-admin//wp-content/plugins/.../.env/backup.zip/wp-config.php.bak/vendor/phpunit/...
Если что-то отвечает интересно, бот идёт глубже. Если получает 403, 404, 429 или закрытое соединение, идёт дальше искать более мягкую цель. Это примерно как домофонный обзвон, только без человека и с гораздо худшими манерами.
Где искать логи Nginx
На обычном сервере с Nginx основные логи чаще всего лежат здесь:
/var/log/nginx/access.log /var/log/nginx/error.log
Если сайт поднят через WordOps, логи обычно отдельные для домена:
/var/log/nginx/example.com.access.log /var/log/nginx/example.com.error.log
Сначала можно просто посмотреть, какие файлы есть в каталоге логов:
ls -lh /var/log/nginx/
Для просмотра свежих строк в реальном времени удобно использовать tail. Например, смотрим обращения к странице входа и XML-RPC:
sudo tail -f /var/log/nginx/example.com.access.log | grep -E "wp-login|xmlrpc"
Если нужны только POST-запросы, то есть фактические отправки формы входа или запросы авторизации, фильтр можно сделать строже:
sudo tail -f /var/log/nginx/example.com.access.log | grep -E 'POST .*(wp-login\.php|xmlrpc\.php)'
Как понять, кто стучит в wp-login.php
/wp-login.php это стандартная страница входа WordPress. Её знают не только администраторы, но и все боты. Поэтому если в логах есть обращения к этому файлу, само по себе это нормально.
Важнее не единичный запрос, а частота. Один IP зашёл пару раз, это может быть человек. Один IP или группа IP делает десятки POST-запросов, это уже похоже на перебор.
Посчитать топ IP по POST-запросам к wp-login.php можно так:
awk '$6 ~ /POST/ && $7 ~ /wp-login\.php/ {print $1}' /var/log/nginx/example.com.access.log | sort | uniq -c | sort -nr | head -20 Если вверху списка десятки или сотни попыток с одного адреса, это уже не «админ забыл пароль». Это бот настойчиво проверяет дверь лбом.
Можно отдельно посмотреть только сегодняшние обращения. Формат даты в Nginx зависит от локали, но часто работает такой вариант:
grep "$(date '+%d/%b/%Y')" /var/log/nginx/example.com.access.log | grep "wp-login.php" | tail -50
Почему xmlrpc.php часто закрывают
/xmlrpc.php это старый интерфейс WordPress для удалённого взаимодействия. Он может быть нужен некоторым приложениям, Jetpack, старым интеграциям и внешним публикациям. Но на большинстве обычных сайтов он вообще не используется.
Боты любят XML-RPC, потому что через него можно делать массовые попытки авторизации и создавать лишнюю нагрузку. Поэтому мой базовый подход простой: если XML-RPC не нужен, его лучше закрыть. Если не уверены, сначала смотрим логи и проверяем, нет ли легитимных обращений.
Посмотреть обращения к XML-RPC можно так:
grep "xmlrpc.php" /var/log/nginx/example.com.access.log | tail -50
Посчитать IP по активности:
awk '$7 ~ /xmlrpc\.php/ {print $1}' /var/log/nginx/example.com.access.log | sort | uniq -c | sort -nr | head -20 Если там постоянный поток запросов от неизвестных адресов, а вы точно не используете XML-RPC, держать его открытым обычно нет смысла.
Что значат коды 200, 403, 404, 429 и 444
В access.log важен не только адрес запроса, но и код ответа. По нему видно, как сервер отреагировал.
200: сервер отдал ответ успешно. Дляwp-login.phpэто не значит, что бот вошёл, просто WordPress обработал запрос.301или302: редирект, например с HTTP на HTTPS.403: доступ запрещён.404: файл не найден. Часто так выглядят поиски старых плагинов и мусорных файлов.429: слишком много запросов, обычно после настройки rate limiting.444: Nginx закрыл соединение без ответа. Жёсткий вариант для мусорных запросов.
Если бот ищет старый плагин и получает 404, это нормально. Если он сотни раз долбит xmlrpc.php и получает 200, это уже повод вмешаться.
Защита WordPress: не один плагин, а несколько слоёв
Мне не нравится подход «поставим один волшебный плагин безопасности, и всё будет хорошо». Так не работает. Защита WordPress должна быть слоёной.
На уровне WordPress мы усложняем вход: меняем стандартный адрес логина, добавляем CAPTCHA, включаем двухфакторную авторизацию. На уровне Nginx ограничиваем частоту запросов и закрываем ненужные точки. На уровне сервера подключаем Fail2ban. Для плохих ботов используем фильтрацию через Nginx Ultimate Bad Bot Blocker.
Смысл простой: не надо заставлять WordPress и PHP обрабатывать весь мусор. Чем раньше мы отсекаем плохой запрос, тем меньше нагрузка на сайт.
Шаг 1. Сменить стандартный адрес входа WordPress
Первое, что часто имеет смысл сделать: убрать вход с очевидного адреса /wp-login.php. Это не защита от серьёзного целевого взлома, но хорошее снижение фонового мусора.
Для этого обычно используют плагины вроде WPS Hide Login или аналогичные решения. Они меняют URL входа, например с /wp-login.php на свой адрес. Важно не делать адрес слишком очевидным вроде /login, /admin или /my-login. Боты тоже не вчера родились.
Пример логики:
Было: https://example.com/wp-login.php Стало: https://example.com/secret-login-panel/
После смены адреса входа нужно обязательно сохранить новый URL в менеджере паролей. Иначе через неделю начнётся знакомая история: «Я сам себя защитил настолько хорошо, что теперь не могу войти».
Ещё важный момент: смена адреса входа не заменяет 2FA, CAPTCHA и нормальные пароли. Она просто убирает часть автоматического мусора, который тупо стучит в стандартный wp-login.php.
Шаг 2. Добавить CAPTCHA или Turnstile на вход
Следующий слой: CAPTCHA. Сейчас я чаще смотрю в сторону Cloudflare Turnstile, потому что он обычно менее раздражающий для пользователя, чем классические капчи с автобусами, светофорами и философским вопросом «где здесь мотоцикл».
CAPTCHA можно добавить через готовый WordPress-плагин. Обычно она ставится на:
- страницу входа;
- форму восстановления пароля;
- форму регистрации, если она включена;
- комментарии;
- формы обратной связи.
Главная идея: если бот просто шлёт POST-запросы пачками, CAPTCHA резко снижает эффективность такого мусора. Но опять же, это не единственная защита. Хорошая безопасность скучна именно потому, что состоит из нескольких простых мер, а не из одной красивой кнопки.
Шаг 3. Включить двухфакторную авторизацию
Двухфакторная авторизация для администраторов WordPress уже давно должна быть нормой. Особенно если сайт рабочий, клиентский или на нём есть формы, заказы, личные кабинеты, SEO-трафик и репутация.
Можно использовать TOTP-коды через приложение-аутентификатор. Например, Aegis, Google Authenticator, Microsoft Authenticator или другой привычный вариант. В WordPress для этого есть плагины вроде Two-Factor и аналогичные решения.
Что важно сделать:
- включить 2FA для всех администраторов;
- проверить резервные коды;
- убрать пользователя
admin, если он ещё существует; - использовать длинные уникальные пароли;
- не хранить пароли в файле на рабочем столе с названием
пароли.txt.
Даже если пароль утечёт, второй фактор сильно усложнит вход. Это не делает сайт бессмертным, но резко снижает риск простого захвата админки.
Шаг 4. Ограничить wp-login.php через Nginx
На уровне Nginx можно ограничить частоту запросов к странице входа. Это полезно даже после смены адреса логина, потому что wp-login.php всё равно остаётся частью WordPress, а некоторые сценарии могут до него доходить.
Сначала в блоке http задаём зону лимита:
limit_req_zone $binary_remote_addr zone=wp_login:10m rate=10r/m;
Потом применяем лимит в конфигурации сайта:
location = /wp-login.php { limit_req zone=wp_login burst=5 nodelay; include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_pass php; } Важно: fastcgi_pass php; здесь только пример. На реальном сервере может быть Unix-сокет, upstream WordOps или другая схема. Перед правкой нужно смотреть текущий конфиг сайта.
После изменения конфигурации проверяем Nginx:
sudo nginx -t
Если ошибок нет, перезагружаем Nginx:
sudo systemctl reload nginx
Потом смотрим, появились ли ответы 429 для слишком активных запросов:
grep ' 429 ' /var/log/nginx/example.com.access.log | tail -30
Шаг 5. Закрыть или ограничить xmlrpc.php
Если XML-RPC не используется, самый простой вариант: закрыть его на уровне Nginx.
location = /xmlrpc.php { return 444; } Код 444 это специфичная штука Nginx: сервер просто закрывает соединение без ответа. Для мусорных запросов это вполне рабочий вариант.
Если не уверены, что XML-RPC можно закрывать полностью, можно сначала поставить лимит:
limit_req_zone $binary_remote_addr zone=xmlrpc:10m rate=5r/m; location = /xmlrpc.php { limit_req zone=xmlrpc burst=3 nodelay; include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_pass php; } Сначала лучше смотреть логи, потом закрывать. Особенно если на сайте есть Jetpack, мобильное приложение WordPress или старые интеграции.
Шаг 6. Fail2ban для автоматической блокировки
Fail2ban полезен, когда нужно автоматически банить IP, которые слишком настойчиво долбят вход. Он читает логи и добавляет блокировку через firewall.
Сначала создаём фильтр:
sudo nano /etc/fail2ban/filter.d/wordpress-login.conf
Пример фильтра под POST-запросы к wp-login.php и xmlrpc.php:
[Definition] failregex = ^- .* "POST /(wp-login\.php|xmlrpc\.php) HTTP/.*" (200|401|403|429) .* ignoreregex =
Потом создаём jail:
sudo nano /etc/fail2ban/jail.d/wordpress-login.conf
Пример jail:
[wordpress-login] enabled = true port = http,https filter = wordpress-login logpath = /var/log/nginx/example.com.access.log backend = auto findtime = 600 maxretry = 10 bantime = 3600 bantime.increment = true bantime.factor = 2 bantime.maxtime = 86400
Проверяем фильтр на реальном логе:
sudo fail2ban-regex /var/log/nginx/example.com.access.log /etc/fail2ban/filter.d/wordpress-login.conf
Если совпадения находятся корректно, перезагружаем Fail2ban:
sudo systemctl reload fail2ban
Проверяем статус:
sudo fail2ban-client status wordpress-login
Разбанить IP можно так:
sudo fail2ban-client set wordpress-login unbanip 203.0.113.10
IP в примере служебный. Реальные адреса из клиентских логов в статьях и публичных материалах лучше не показывать. Зачем кормить интернет лишними данными, он и так прожорливый.
Шаг 7. Nginx Ultimate Bad Bot Blocker
Отдельный слой, который я предлагаю для сайтов на Nginx, это Nginx Ultimate Bad Bot Blocker. Это набор конфигураций для отсечения плохих ботов, вредных user-agent, referrer spam, сканеров и части мусорного трафика ещё до WordPress.
Смысл в том, чтобы WordPress не тратил ресурсы на обработку заведомо мусорных запросов. Если плохой бот отсекается на уровне Nginx, до PHP он вообще не доходит. Для VPS это особенно полезно: меньше нагрузка, чище логи, спокойнее сервер.
Но ставить такой блокировщик нужно аккуратно. Это не «залил и забыл». После внедрения обязательно проверяем:
- главную страницу;
- админку WordPress;
- формы обратной связи;
- REST API;
- cron-задачи;
- оплату, если есть магазин;
- внешние интеграции;
- логи Nginx после включения фильтрации.
Любая защита может дать ложное срабатывание, если включать её без проверки. Хорошая защита должна резать мусор, а не нормальных пользователей.
Шаг 8. Проверить пользователей WordPress
Боты часто перебирают стандартные логины. Если на сайте есть пользователь admin, это подарок для перебора. Его лучше не использовать.
Через WP-CLI можно посмотреть пользователей так:
wp user list --fields=ID,user_login,user_email,roles
Если есть старые администраторы, лишние аккаунты или непонятные пользователи, их нужно разобрать. На клиентских сайтах это встречается часто: сайт делали несколько подрядчиков, каждый оставил себе доступ, потом все забыли, а аккаунты продолжают жить. Прямо маленькое кладбище логинов.
Перед удалением пользователя важно передать его записи другому аккаунту, чтобы не потерять контент.
Шаг 9. Обновить WordPress, тему и плагины
Защищать WordPress и при этом держать старые плагины без обновлений странно. Это как поставить железную дверь и оставить окно открытым.
Минимально нужно проверить:
- версию WordPress;
- версию PHP;
- актуальность темы;
- обновления плагинов;
- наличие неиспользуемых тем и плагинов;
- работу резервного копирования.
Перед крупными обновлениями обязательно делаем бэкап. Если есть WP-CLI, базу можно выгрузить так:
wp db export ~/backup-before-security-update.sql
Файлы сайта можно сохранить архивом:
tar -czf ~/site-files-before-security-update.tar.gz /var/www/example.com/htdocs
Пути нужно заменить под свой сервер. На WordOps, Hestia, Plesk и обычном ручном Nginx структура может отличаться.
Как я бы объяснил это клиенту простыми словами
Если клиент видит такие логи и спрашивает, «у всех ли так», я бы ответил спокойно:
Да, это обычная картина для WordPress-сайта в интернете. Боты постоянно проверяют стандартные адреса входа, XML-RPC, старые плагины, архивы, служебные файлы и известные уязвимости. Само наличие таких строк в логе не означает, что сайт взломали. Но это значит, что сайт нужно защищать нормально, а не надеяться, что его никто не заметит.
Я предлагаю выстроить несколько уровней защиты: скрыть стандартный вход, добавить CAPTCHA, включить двухфакторную авторизацию, ограничить частоту запросов в Nginx, закрыть или ограничить XML-RPC, настроить Fail2ban и поставить блокировку плохих ботов на уровне Nginx. Это снизит нагрузку, уменьшит шум в логах и сделает автоматические атаки менее эффективными.
Проверка после внедрения защиты
После настройки защиты нельзя просто закрыть терминал и уйти пить чай. Нужно проверить, что сайт работает нормально.
- Открывается главная страница.
- Открываются записи и страницы.
- Работает новая страница входа.
- Администратор может войти с 2FA.
- Работают формы.
- Не сломались REST API и cron.
- В логах нет массовых ошибок для нормальных пользователей.
- Подозрительные запросы получают 403, 429 или 444.
Полезно отдельно посмотреть последние ошибки Nginx:
sudo tail -n 100 /var/log/nginx/example.com.error.log
И свежие запросы к входу:
grep -E "wp-login|xmlrpc" /var/log/nginx/example.com.access.log | tail -50
Краткий чеклист
- Посмотреть access.log и error.log.
- Найти обращения к
wp-login.phpиxmlrpc.php. - Посчитать топ IP по POST-запросам.
- Сменить стандартный адрес входа WordPress.
- Добавить CAPTCHA или Turnstile на вход.
- Включить 2FA для администраторов.
- Закрыть или ограничить
xmlrpc.php. - Настроить rate limiting в Nginx.
- Добавить Fail2ban для грубых переборов.
- Установить Nginx Ultimate Bad Bot Blocker и проверить сайт после внедрения.
- Обновить WordPress, тему и плагины.
- Проверить бэкапы и возможность отката.
Вывод
Ещё раз про безопасность на WordPress: боты в логах это не исключение, а нормальный фон интернета. Если сайт доступен снаружи, его будут сканировать. Вопрос не в том, как полностью убрать ботов из жизни, а в том, как сделать так, чтобы они не мешали сайту работать и не получали лишних шансов.
Мой подход простой: не верить в одну волшебную кнопку. Смена адреса входа снижает шум. CAPTCHA режет автоматические отправки. 2FA защищает админов при утечке пароля. Nginx ограничивает частоту запросов. Fail2ban банит настойчивых. Bad Bot Blocker отсекает известный мусор ещё до WordPress.
Такой сайт не становится неуязвимым. Но он перестаёт быть мягкой целью. А в реальном интернете это уже очень много. Боты любят халяву, старые плагины и открытые двери. Не надо им устраивать праздник.