Jail активен, служба здорова, вывод статуса чистый — и счётчик банов уже несколько дней на нуле. На сервере с WordPress это не хорошая новость: почти всегда это значит, что фильтр не совпадает с форматом ваших логов.
Штатные фильтры 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 для захвата IPwp-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 — это нормально; те строки относятся к обычному трафику.
В /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, и в лог записывается 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 находит совпадения в моём реальном логе, и счётчик банов растёт». Пока это не проверено, защита остаётся предположением.
Total banned: 0, ошибок нет^<HOST> не совпадает^"?(?:\S+ )?<HOST> - - покрывает обе формыfail2ban-regex лог фильтр — ноль совпадений = мёртвое правилоwp-login.php и xmlrpc.php, вместе GET и POSTlogpath = /home/*/logs/*.access_logПочти всегда шаблон фильтра не совпадает с форматом ваших логов. В логах OpenLiteSpeed строка часто начинается с имени vhost, а штатные фильтры WordPress ожидают IP в начале. Не найдя совпадений, fail2ban не выдаёт ошибку — он просто никого не банит. Проверьте через fail2ban-regex: если matched равен нулю, причина эта.
Запустите fail2ban-regex на вашем реальном журнале доступа и вашем файле фильтра, затем прочитайте число matched. Ноль означает, что шаблон не работает. Большое число означает, что шаблон верен, и заодно вы узнаете реальный объём трафика перебора паролей на вашем сайте.
Без дополнительной настройки он приносит больше вреда, чем пользы. В логи пишется IP CDN, поэтому fail2ban банит CDN, а не атакующего, и один бан может отрезать всех посетителей. Добавьте диапазоны CDN в ignoreip и настройте веб-сервер так, чтобы он логировал реальный IP посетителя из заголовка CDN.
Они работают на разных уровнях. Плагин видит запрос только после того, как отработал PHP, то есть каждая попытка всё равно тратит ресурсы сервера. Fail2ban отсекает IP на уровне брандмауэра, и запрос вообще не доходит до PHP. При интенсивном переборе разница в потреблении ресурсов существенна.