Если у вас есть WordPress, вы уже видите постоянные попытки bruteforce на wp-login.php и xmlrpc.php. Простое скрытие URL не решает проблему надолго: сканеры быстро находят вход. Рабочая комбинация выглядит так: срезаем трафик лимитами (Nginx rate limit), отсекаем агрессивных источников на уровне брандмауэра (fail2ban), а внутри приложения делаем вход бесполезным без второго фактора (2FA). Ниже — пошагово, с примерами конфигов и рекомендациями для продакшна.
Почему именно три слоя: Nginx + fail2ban + 2FA
Один слой защиты почти всегда либо слишком мягкий (не снижает нагрузку на PHP), либо слишком жёсткий (ломает легитимный трафик), либо бессилен против украденных паролей. Комбинация даёт баланс:
- Nginx моментально режет всплески и защищает PHP-FPM от штормов запросов.
- fail2ban выносит источники bruteforce в бан по сетевому уровню — экономит CPU и I/O.
- 2FA в WordPress нейтрализует украденные пароли и утечки.
Результат: устойчивость к атакам, предсказуемая нагрузка и минимальные ложные срабатывания при корректных лимитах.
Слой 1. Rate limit в Nginx для wp-login и xmlrpc
Базовая зона и применение
Создадим зону лимитов по IP и применим её к /wp-login.php и /xmlrpc.php. Выберите базовые пороги, от которых удобно стартовать на бою, а затем адаптируйте под аудиторию.
# http
limit_req_zone $binary_remote_addr zone=wp_zone:10m rate=30r/m;
limit_req_zone $binary_remote_addr zone=xmlrpc_zone:10m rate=10r/m;
# Ограничение параллельных соединений от одного IP
limit_conn_zone $binary_remote_addr zone=perip:10m;
Применение к нужным локациям:
# server
location = /wp-login.php {
limit_req zone=wp_zone burst=10 nodelay;
limit_conn perip 5;
include fastcgi_params;
# Дальше — ваш fastcgi_pass и пр.
}
location = /xmlrpc.php {
limit_req zone=xmlrpc_zone burst=5 nodelay;
limit_conn perip 2;
return 444;
}
Возврат 444 для xmlrpc — агрессивный, но эффективный выбор, если он не нужен легальным интеграциям. Если xmlrpc требуется, оставьте обработку PHP, но сохраняйте низкие лимиты.
Мягкое обходное для своих IP
Чтобы не мешать редакторам и администраторам из доверенных сетей, добавьте «тумблер» лимита для своих адресов. Удобно делать через geo и управлять лимитом параметром if= в limit_req.
# http
geo $remote_addr $wp_limit_on {
default 1;
192.0.2.10 0; # пример: офис
198.51.100.0/24 0; # пример: VPN-пул
}
# server
location = /wp-login.php {
limit_req zone=wp_zone burst=10 nodelay if=$wp_limit_on;
limit_conn perip 5;
include fastcgi_params;
}
Если сервер работает за обратным прокси/CDN, настройте корректную идентификацию клиента: real_ip_header и доверенные адреса через set_real_ip_from, чтобы $remote_addr был реальным IP пользователя.
Логирование 429 и пользовательский ответ
Видимый «человеческий» ответ при 429 помогает понять ситуацию легитимному пользователю.
# http или server
error_page 429 = @limit429;
location @limit429 {
add_header Retry-After 60 always;
return 429 "Too Many Requests\n";
}
Если вы на управляемом хостинге и нет root-доступа, проверьте, доступны ли лимиты на уровне панели или поддержки. На отдельном VDS можно гибко управлять Nginx и брандмауэром под свой трафик.

Слой 2. Блокировки fail2ban по логам Nginx
Установка и запуск
sudo apt-get update
sudo apt-get install fail2ban -y
sudo systemctl enable --now fail2ban
Фильтр по access.log для wp-login и xmlrpc
Простой, но действенный подход — считать агрессивным поведение, когда IP многократно шлёт POST на /wp-login.php или долбит /xmlrpc.php. Создайте фильтр.
# /etc/fail2ban/filter.d/wordpress-login.conf
[Definition]
# Примеры для комбинированного формата Nginx ("combined")
failregex = ^<HOST> - - \[.*\] "POST /wp-login\.php HTTP/.*" (200|302)
^<HOST> - - \[.*\] "POST /xmlrpc\.php HTTP/.*" (200|403|444)
ignoreregex =
Шаблон выше универсален, но подстройте его под ваш
log_format. Если включены другие поля или иной порядок колонок — скорректируйте регулярки.
Jail с нарастающим временем бана
# /etc/fail2ban/jail.d/wordpress-login.conf
[wordpress-login]
enabled = true
port = http,https
filter = wordpress-login
logpath = /var/log/nginx/access.log
backend = auto
findtime = 10m
maxretry = 10
bantime = 1h
bantime.increment = true
bantime.factor = 2
bantime.maxtime = 24h
Если на сервере используется nftables по умолчанию, укажите бэкенд действий:
# /etc/fail2ban/jail.d/actions-nft.conf
[DEFAULT]
action = nftables-multiport[name=wp, port="http,https"]
Проверка фильтра и статуса
sudo fail2ban-regex /var/log/nginx/access.log /etc/fail2ban/filter.d/wordpress-login.conf
sudo fail2ban-client reload
sudo fail2ban-client status
sudo fail2ban-client status wordpress-login
Если Nginx стоит за обратным прокси/CDN, убедитесь, что в
access.logзаписывается реальный клиентский IP. Иначе fail2ban будет банить адрес прокси.
На уровне сервера жёсткие политики SSH и фаерволла хорошо дополняют эту схему. Подробнее см. материал Безопасность VDS: SSH и firewall.
Добавочный фильтр на 429
Полезно банить тех, кто регулярно попадает под лимит Nginx (код 429):
# /etc/fail2ban/filter.d/nginx-429.conf
[Definition]
failregex = ^<HOST> - - \[.*\] ".*" 429
ignoreregex =
# /etc/fail2ban/jail.d/nginx-429.conf
[nginx-429]
enabled = true
port = http,https
filter = nginx-429
logpath = /var/log/nginx/access.log
findtime = 5m
maxretry = 20
bantime = 30m
Слой 3. 2FA в WordPress
Даже с лимитами и банами пароль может быть украден вне сайта. Двухфакторная аутентификация (2FA) закрывает этот сценарий. Оптимальный вариант для WordPress — TOTP (аутентификаторы по одноразовым кодам).
- Включите 2FA для всех админов и редакторов, а при возможности — сделайте обязательным.
- Сохраните резервные коды офлайн, обучите команду процедуре восстановления.
- Синхронизируйте время сервера (NTP/chrony): TOTP чувствителен к сдвигам времени.
- Проверьте, распространяется ли 2FA на REST и xmlrpc. Если нет — отключите или ограничьте
/xmlrpc.php, используйте «пароли приложений» и жёсткие лимиты.
2FA не заменяет парольную политику. Минимум: уникальные длинные пароли и менеджер паролей для команды.

Как слои работают вместе
Порядок срабатывания важен для эффективности:
- Nginx первым режет частоту запросов и закрывает xmlrpc при необходимости. На этом этапе PHP даже не просыпается.
- fail2ban читает логи и выносит в бан IP, которые не внемлют лимитам и продолжают долбить wp-login/xmlrpc.
- Даже если пароль украли и попытки единичные (под лимитами), 2FA остановит вход.
Для ускорения реакции учтите 429 в фильтрах fail2ban (см. выше), чтобы агрессоры улетали в бан быстрее.
Практические пресеты лимитов
Стартовые значения, на которых редко бывают жалобы пользователей, и которые заметно снижают шум bruteforce:
/wp-login.php:rate=30r/m,burst=10,limit_conn perip 5./xmlrpc.php:rate=10r/m,burst=5, а лучше —return 444при ненужности.- fail2ban:
maxretry=10за10m,bantime=1hс инкрементом до суток.
Оцените свою пиковую аудиторию: если у вас много редакторов, расширьте allowlist или сделайте обходной лимит для их подсетей.
Учёт IPv6, прокси и формат логов
- IPv6: включите блокировки и для него (nftables/iptables v6). Проверьте, что fail2ban видит IPv6 в логах.
- Обратные прокси/CDN: корректно настраивайте
real_ip_headerиset_real_ip_from, а также логируйте реальный клиентский адрес. - Единый формат логов: храните
access.logв одном формате и укажите его в фильтре. Любые изменения — синхронно правьте regex.
Тестирование и отладка
Проверьте, что лимиты Nginx срабатывают:
# 10 быстрых POST-запросов должны вызывать 429 часть времени
for i in $(seq 1 10); do curl -s -o /dev/null -w "%{http_code}\n" -X POST http://127.0.0.1/wp-login.php; done
Смотрите логи и статус банов:
sudo tail -f /var/log/nginx/access.log
sudo fail2ban-client status wordpress-login
sudo fail2ban-client set wordpress-login unbanip 203.0.113.5
Надёжность и эксплуатация
- Память зон
limit_req_zone: 10 МБ хватает примерно на сотни тысяч ключей; если у вас много уникальных IP — увеличьте. - Ротация логов: убедитесь, что fail2ban видит новые файлы после
logrotate. При необходимости перезагружайте fail2ban. - Recidive: подключите «рецидив» для долговременных нарушителей — jail, отслеживающий ранее забаненных.
- Мониторинг: отслеживайте количество 429, скорость банов и число активных записей в зонах ограничений.
Быстрый чеклист внедрения
- Включите лимиты Nginx для
/wp-login.phpи/xmlrpc.php, настройте allowlist и страницу 429. - Разверните fail2ban, добавьте фильтры по
wp-login,xmlrpcи, опционально, по 429. - Настройте 2FA в WordPress, включите для ролей администратор/редактор, подготовьте резервные коды.
- Проверьте корректность реального IP в логах при работе за прокси/CDN.
- Протестируйте нагрузкой, посмотрите логи и отрегулируйте пороги.
Такой трёхслойный щит решает 95% обычных атак на WordPress: вы разгружаете PHP, быстро блокируете агрессоров и делаете вход бессмысленным без второго фактора. Остальные 5% — это эксплуатация уязвимостей плагинов и тем: держите обновления в порядке и не давайте злоумышленникам второго шанса.


