Fail2Ban для веб-сервисов: кастомные jail-фильтры для Nginx, Apache и приложений за пределами SSH

Почему SSH-защиты недостаточно​


Fail2Ban из коробки часто настраивают только на sshd jail. Но веб-сервер — не менее привлекательная цель: брутфорс панелей управления, перебор паролей в /wp-login.php, сканирование уязвимостей по сигнатурам в URI, попытки эксплуатации известных CVE через GET/POST-запросы. Все эти атаки оставляют следы в логах Nginx или Apache, и Fail2Ban способен их автоматически блокировать.

Принцип работы не меняется: демон читает лог-файл, применяет регулярное выражение (фильтр), считает совпадения по IP и при превышении порога вызывает действие (обычно iptables или nftables ban). Разница лишь в том, какой лог парсить и какой паттерн искать.

Структура конфигурации Fail2Ban​


Конфигурация строится из трёх слоёв:

КомпонентРасположениеНазначение
Фильтр/etc/fail2ban/filter.d/*.confРегулярное выражение для извлечения IP из строки лога
Jail/etc/fail2ban/jail.conf + /etc/fail2ban/jail.localПривязка фильтра к лог-файлу, пороги, время бана
Действие/etc/fail2ban/action.d/*.confЧто делать при бане (iptables, nftables, firewallcmd)

Правило: никогда не редактируйте jail.conf напрямую. Все переопределения пишите в jail.local или в файлы /etc/fail2ban/jail.d/*.conf.

Встроенные фильтры для Nginx​


Пакет Fail2Ban уже содержит несколько фильтров для Nginx. Проверить их наличие:

Bash:
ls /etc/fail2ban/filter.d/nginx-*.conf

Типичный набор:

  • nginx-http-auth.conf — парсит строки user "..." was not found in "..." и password mismatch из error-лога при использовании auth_basic.
  • nginx-bad-request.conf — ловит запросы, которые Nginx отклонил с кодом 400.
  • nginx-botsearch.conf — блокирует ботов, сканирующих типовые пути (/phpmyadmin, /wp-admin и т. п.).
  • nginx-limit-req.conf — работает в связке с директивой limit_req в Nginx: банит IP, которые превысили лимит запросов.

Активация nginx-http-auth​


В /etc/fail2ban/jail.local:

INI:
[nginx-http-auth]
enabled  = true
port     = http,https
logpath  = /var/log/nginx/error.log
maxretry = 5
bantime  = 3600
findtime = 600

Этот jail сработает, если в конфигурации Nginx включена auth_basic и кто-то подбирает пароль. Строка в error-логе выглядит примерно так:

Код:
2024/01/15 03:22:11 [error] 1234#1234: *5678 user "admin" was not found in "/etc/nginx/.htpasswd", client: 203.0.113.50, server: example.com, request: "GET /admin HTTP/1.1"

Фильтр извлекает IP из поля client:.

Активация nginx-limit-req​


Сначала в конфиге Nginx нужно определить зону:

NGINX:
http {
    limit_req_zone $binary_remote_addr zone=perip:10m rate=10r/s;

    server {
        location / {
            limit_req zone=perip burst=20 nodelay;
        }
    }
}

При превышении лимита Nginx пишет в error-лог строку с limiting requests. Jail:

INI:
[nginx-limit-req]
enabled  = true
port     = http,https
logpath  = /var/log/nginx/error.log
maxretry = 10
bantime  = 600
findtime = 600

Здесь maxretry выше обычного, потому что один клиент может случайно превысить лимит при загрузке страницы с множеством ресурсов.

Кастомный фильтр: брутфорс HTTP-авторизации приложения​


Встроенные фильтры покрывают auth_basic Nginx, но многие приложения реализуют собственную авторизацию и пишут в access-лог код ответа 401 или 403. Пример — кастомный фильтр для приложения, которое логирует неудачные попытки в отдельный файл.

Шаг 1. Определяем паттерн в логе​


Допустим, приложение пишет в /var/log/myapp/auth.log:

Код:
2024-01-15 03:25:01 AUTH FAILURE ip=203.0.113.50 user=admin reason=invalid_password

Шаг 2. Создаём фильтр​


Файл /etc/fail2ban/filter.d/myapp-auth.conf:

INI:
[Definition]
failregex = ^.* AUTH FAILURE ip=<HOST> .*$
ignoreregex =

<HOST> — специальный плейсхолдер Fail2Ban, который заменяется на регулярное выражение для IPv4/IPv6.

Шаг 3. Проверяем фильтр утилитой fail2ban-regex​


Bash:
fail2ban-regex /var/log/myapp/auth.log /etc/fail2ban/filter.d/myapp-auth.conf

Вывод покажет количество совпадений. Если Matches: 0 — паттерн не находит строки, нужно корректировать failregex.

Шаг 4. Создаём jail​


INI:
[myapp-auth]
enabled  = true
port     = http,https
logpath  = /var/log/myapp/auth.log
maxretry = 5
bantime  = 3600
findtime = 300

Перезапуск:

Bash:
systemctl restart fail2ban

Кастомный фильтр для Apache​


Apache пишет ошибки авторизации в error-лог в другом формате. Строка при неудачной AuthType Basic:

Код:
[Thu Jan 15 03:30:22.123456 2024] [auth_basic:error] [pid 5678] [client 203.0.113.50:54321] AH01618: user admin not found: /secret/

Встроенный фильтр apache-auth.conf уже обрабатывает этот формат. Достаточно включить jail:

INI:
[apache-auth]
enabled  = true
port     = http,https
logpath  = /var/log/apache2/error.log
maxretry = 5
bantime  = 3600

Для кастомных приложений за Apache, которые пишут 401 в access-лог, создайте фильтр по аналогии с примером выше, указав logpath на access-лог и паттерн на строку с кодом 401.

Защита от сканеров уязвимостей​


Сканеры типа Nikto, dirsearch, gobuster генерируют сотни запросов к несуществующим путям. В access-логе Nginx это выглядит как множество записей с кодом 404 за короткое время.

Фильтр по массовым 404​


Файл /etc/fail2ban/filter.d/nginx-404-scan.conf:

INI:
[Definition]
failregex = ^<HOST> - .*"\s+404\s+\d+.*$
ignoreregex =

Этот паттерн предполагает стандартный формат combined access-лога Nginx. Jail:

INI:
[nginx-404-scan]
enabled  = true
port     = http,https
logpath  = /var/log/nginx/access.log
maxretry = 50
bantime  = 86400
findtime = 300

maxretry = 50 за 5 минут — порог, который не заденет обычного пользователя, но остановит сканер.

Ограничения подхода​


  • Если сайт отдаёт 404 для легитимных ресурсов (битые ссылки, старые URL), порог нужно поднимать.
  • Для SPA-приложений с client-side routing лучше использовать nginx-limit-req вместо подсчёта 404.

Работа с несколькими лог-файлами и ротацией​


Fail2Ban читает лог по пути, указанному в logpath. Если logrotate переименовывает файл, демон может потерять позицию. Решения:

  • Используйте backend = systemd вместо чтения файлов, если сервис пишет в journald.
  • Для файлов убедитесь, что logrotate настроен с copytruncate или что Fail2Ban перечитывает файл после ротации (по умолчанию он это делает через inotify или polling).

Проверить, что Fail2Ban видит лог:

Bash:
fail2ban-client status nginx-http-auth

Вывод покажет количество забаненных IP и текущее состояние.

Действия: iptables vs nftables vs firewalld​


По умолчанию Fail2Ban использует iptables. На системах с nftables (Debian 11+, Ubuntu 22.04+ по умолчанию) укажите в jail.local:

INI:
[DEFAULT]
banaction = nftables[type=multiport]
banaction_allports = nftables[type=allports]

На системах с firewalld (RHEL, CentOS, Fedora):

INI:
[DEFAULT]
banaction = firewallcmd-rich-rules
banaction_allports = firewallcmd-rich-rules[actiontype=<rich-rule>]

Инкрементальный бан и белые списки​


Увеличение времени бана при повторных нарушениях​


Начиная с Fail2Ban 0.11, доступна опция bantime.increment:

INI:
[DEFAULT]
bantime.increment = true
bantime.factor    = 2
bantime.maxtime   = 604800

При каждом новом бане время удваивается, но не превышает 7 дней.

Белый список​


INI:
[DEFAULT]
ignoreip = 127.0.0.1/8 ::1 192.168.1.0/24

IP из ignoreip никогда не будут забанены. Добавляйте сюда адреса мониторинга, CI/CD-раннеров и офисные подсети.

Диагностика и типовые ошибки​


СимптомВероятная причинаРешение
Jail не банит, хотя атаки видны в логеfailregex не совпадает с форматом строкиПроверить через fail2ban-regex
No such file or directory в логе Fail2BanНеверный logpath или файл ещё не созданУбедиться, что путь существует и доступен на чтение
Бан есть, но трафик продолжает идтиБан применяется не к той цепочке или интерфейс не тотПроверить iptables -L f2b-<jailname> или nft list ruleset
Легитимные пользователи попадают в банСлишком низкий maxretry или слишком широкий failregexПоднять порог, сузить паттерн, добавить ignoreip

Полезные команды диагностики:

Bash:
## Статус всех jail
fail2ban-client status

## Статус конкретного jail
fail2ban-client status nginx-http-auth

## Разбанить IP
fail2ban-client set nginx-http-auth unbanip 203.0.113.50

## Просмотр лога Fail2Ban
tail -f /var/log/fail2ban.log

Производительность при большом потоке логов​


Если access-лог Nginx получает тысячи строк в секунду, парсинг регулярными выражениями создаёт нагрузку. Рекомендации:

  • Используйте backend = polling только если inotify недоступен; inotify эффективнее.
  • Ограничьте количество jail, читающих один и тот же файл, или объединяйте паттерны в один фильтр с несколькими failregex.
  • Для высоконагруженных систем рассмотрите backend = systemd с фильтрацией по unit — это снимает нагрузку на файловый I/O.

Чек-лист развёртывания​


  1. Убедитесь, что нужные лог-файлы существуют и пишутся.
  2. Создайте или активируйте фильтр; проверьте его через fail2ban-regex.
  3. Добавьте jail в jail.local с разумными порогами.
  4. Укажите корректный banaction для вашей системы (iptables / nftables / firewalld).
  5. Добавьте ignoreip для доверенных адресов.
  6. Перезапустите Fail2Ban и проверьте fail2ban-client status <jail>.
  7. Сымитируйте несколько неудачных попыток авторизации с тестового IP и убедитесь, что бан срабатывает.
  8. Настройте мониторинг /var/log/fail2ban.log на предмет ошибок парсинга.

Источники​


 
Назад
Верх Низ