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