Доступен — Локальные цифровые продукты
HCA · Studio
TR — Контакты ↗
Заметка 09 · Безопасность

fail2ban работает и не банит никого

Jail активен, служба здорова, вывод статуса чистый — и счётчик банов уже несколько дней на нуле. На сервере с WordPress это не хорошая новость: почти всегда это значит, что фильтр не совпадает с форматом ваших логов.

Сервер
OpenLiteSpeed
Симптом
Total banned: 0
Проверка
fail2ban-regex
Короткий ответ

Штатные фильтры WordPress рассчитаны на объединённый формат логов Apache, где строка начинается с IP клиента. В OpenLiteSpeed формат задаётся на уровне vhost, и во многих установках строка начинается с имени vhost ("example.com 1.2.3.4 - - [..."), а не с IP. Шаблон, закреплённый как ^<HOST>, тогда не совпадает ни с чем — а не найдя совпадений, fail2ban не выдаёт ошибку, он просто никого не банит. Решение — шаблон, допускающий необязательное поле в начале, проверенный на вашем реальном логе через fail2ban-regex.

Симптом

Всё установлено, jail описан, служба работает. Вы запрашиваете статус:

fail2ban-client status wordpress-ols

Вывод выглядит нормально: jail включён, файл отслеживается, ошибок нет. Но две строки бросаются в глаза: Currently failed: 0 и Total banned: 0.

На публичном сайте с WordPress попытки к wp-login.php и xmlrpc.php идут непрерывно. Если эти числа держатся на нуле днями, это не значит, что атак нет; это значит, что фильтр не может прочитать ваши строки лога.

Разница форматов

Штатные фильтры apache-* и распространённые фильтры WordPress исходят из того, что строка начинается прямо с IP клиента:

1.2.3.4 - - [18/Sep/2026:07:14:22 +0000] "POST /wp-login.php HTTP/2" 200 1234

В OpenLiteSpeed формат зависит от строки logFormat в конфигурации vhost, и установки через панель часто подставляют имя vhost в начало:

"example.com 1.2.3.4 - - [18/Sep/2026:07:14:22 +0000] "POST /wp-login.php HTTP/2" 200 1234"

Разница в одно поле, но для шаблона, закреплённого на ^<HOST>, она фатальна: в начале строки нет IP, значит совпадений не будет никогда.

Хуже то, что два сервера одного администратора могут использовать два разных формата. Скопируйте рабочий фильтр с одного на другой — и он молча умрёт. Поэтому фильтр нужно писать по реальной строке на этом сервере, а не по предположению.

Устойчивый фильтр

Подход, покрывающий обе формы, — допустить необязательное поле в начале строки. В /etc/fail2ban/filter.d/wordpress-ols.conf:

[Definition]
failregex = ^"?(?:\S+ )?<HOST> - -.*"(?:GET|POST|HEAD) [^"]*(?:wp-login\.php|xmlrpc\.php)
ignoreregex =
datepattern = %%d/%%b/%%Y:%%H:%%M:%%S %%z

Как читать шаблон:

  • ^"? — некоторые форматы оборачивают строку в кавычки; считаем это необязательным
  • (?:\S+ )? — проглатывает поле vhost, если оно есть, и не мешает, если его нет
  • <HOST> — специальный плейсхолдер fail2ban для захвата IP
  • wp-login\.php|xmlrpc\.php — две классические цели перебора
  • %%d/%%b/… — знаки процента удвоены намеренно: одиночный % fail2ban считает переменной и не загружает фильтр. Если убрать строку совсем, fail2ban распознает дату сам.

Ловятся и GET, и POST: атакующие сначала прощупывают страницу входа через GET.

Не внедряйте непроверенное

Это самое важное предложение заметки: не включайте фильтр, который вы не проверили на собственном файле логов. В fail2ban для этого есть готовый инструмент:

fail2ban-regex /home/example.com/logs/example.com.access_log \
               /etc/fail2ban/filter.d/wordpress-ols.conf

Смотрите на два числа в выводе:

Lines: 128934 lines, 0 ignored, 36925 matched, 92009 missed

Если matched равен нулю, шаблон не работает и включать jail бессмысленно. Если совпадений тысячи — шаблон верен, и заодно вы узнали реальный объём атак на ваш сайт.

Высокое значение missed — это нормально; те строки относятся к обычному трафику.

Настройка jail

В /etc/fail2ban/jail.local:

[wordpress-ols]
enabled  = true
port     = http,https
filter   = wordpress-ols
logpath  = /home/*/logs/*.access_log
maxretry = 5
findtime = 600
bantime  = 3600
ignoreip = 127.0.0.1/8 ::1

logpath принимает шаблоны; на сервере с множеством сайтов используйте маску вместо перечисления каждого vhost — новые сайты покроются автоматически.

После включения перезагрузите конфигурацию и убедитесь, что работа действительно идёт:

fail2ban-client reload
fail2ban-client status wordpress-ols

В течение нескольких часов Total banned должен начать расти. Если он по-прежнему ноль — либо фильтр не совпадает, либо лог не пишется; во втором случае проверьте права на каталог логов.

Ловушка CDN

Если сайт стоит за CDN, соединение до вашего сервера приходит не от посетителя, а от узла CDN, и в лог записывается IP этого узла. В таком случае fail2ban банит CDN, а не атакующего — и один бан способен отрезать всех посетителей.

Нужны две меры одновременно. Сначала исключите диапазоны CDN из блокировки:

ignoreip = 127.0.0.1/8 ::1 173.245.48.0/20 103.21.244.0/22 ...

Затем добейтесь, чтобы в лог писался реальный IP посетителя. CDN передаёт его в заголовке, и веб-сервер нужно настроить так, чтобы он считал этот заголовок адресом клиента. В OpenLiteSpeed это опция использования исходного IP-заголовка. Иначе баны бьют не по той цели, а статистика теряет смысл.

Итог

Опасность fail2ban в том, что неправильно настроенная установка выглядит как рабочая. Служба поднята, jail активен, ошибок нет — и защиты ноль. Поэтому последний шаг настройки — не «я запустил службу», а «fail2ban-regex находит совпадения в моём реальном логе, и счётчик банов растёт». Пока это не проверено, защита остаётся предположением.

Быстрая справка
Симптом
Jail активен, Total banned: 0, ошибок нет
Причина
Строка лога OLS начинается с имени vhost; ^<HOST> не совпадает
Устойчивый шаблон
^"?(?:\S+ )?<HOST> - - покрывает обе формы
Обязательный шаг
fail2ban-regex лог фильтр — ноль совпадений = мёртвое правило
Цели
wp-login.php и xmlrpc.php, вместе GET и POST
Много сайтов
Маска: logpath = /home/*/logs/*.access_log
За CDN
Диапазоны CDN должны быть в ignoreip, иначе забаните сам CDN
Предусловие
Нет записи в лог — fail2ban слеп; сначала проверьте права
Частые вопросы

О fail2ban и OpenLiteSpeed.

Jail выглядит активным, но банов нет. В чём дело?

Почти всегда шаблон фильтра не совпадает с форматом ваших логов. В логах OpenLiteSpeed строка часто начинается с имени vhost, а штатные фильтры WordPress ожидают IP в начале. Не найдя совпадений, fail2ban не выдаёт ошибку — он просто никого не банит. Проверьте через fail2ban-regex: если matched равен нулю, причина эта.

Как правильно проверить фильтр?

Запустите fail2ban-regex на вашем реальном журнале доступа и вашем файле фильтра, затем прочитайте число matched. Ноль означает, что шаблон не работает. Большое число означает, что шаблон верен, и заодно вы узнаете реальный объём трафика перебора паролей на вашем сайте.

Мой сайт за Cloudflare. Fail2ban всё ещё полезен?

Без дополнительной настройки он приносит больше вреда, чем пользы. В логи пишется IP CDN, поэтому fail2ban банит CDN, а не атакующего, и один бан может отрезать всех посетителей. Добавьте диапазоны CDN в ignoreip и настройте веб-сервер так, чтобы он логировал реальный IP посетителя из заголовка CDN.

Разве плагина безопасности WordPress недостаточно?

Они работают на разных уровнях. Плагин видит запрос только после того, как отработал PHP, то есть каждая попытка всё равно тратит ресурсы сервера. Fail2ban отсекает IP на уровне брандмауэра, и запрос вообще не доходит до PHP. При интенсивном переборе разница в потреблении ресурсов существенна.

Доступность и смета

Есть идея?
Можно даже одной фразой.