Шаг 13 · Защита · 30 минут

Fail2ban: продвинутая защита VPS от брутфорса

Базовая настройка SSH-джейла закрывает самый частый случай перебора паролей. Здесь пойдём дальше: свои джейлы, фильтры под конкретные сервисы и разбор действий, если Fail2ban забанил не того.

Что уже настроено

В гайде по базовой защите SSH мы уже включили джейл sshd — Fail2ban читает /var/log/auth.log, считает неудачные попытки входа и банит IP через iptables. Для одиночного сервера с закрытым SSH-паролем этого часто достаточно.

Но Fail2ban — это не только про SSH. Ниже — что стоит добавить, если на сервере крутится сайт за Nginx, своё веб-приложение с логом авторизации, или если один пропущенный лог-файл после ротации внезапно всё ломает.

Как устроены джейлы и фильтры

У Fail2ban три ключевых места, где всё описано:

/etc/fail2ban/jail.local  — какие джейлы включены и с какими параметрами
/etc/fail2ban/filter.d/  — regex-фильтры: как вытащить IP из строки лога
/var/log/fail2ban.log  — свой лог демона: кто, когда и почему забанен

Джейл — это связка «какой лог читать» + «каким фильтром искать нарушения» + «что делать при срабатывании». Фильтр — это regex-шаблон с именем группы <HOST>, которая должна совпасть с IP-адресом в строке лога. Настройки лучше держать в jail.local, а не в jail.conf — этот файл могут перезаписать при обновлении пакета.

Джейл для Nginx

Боты и сканеры перебирают пути вида /wp-admin, /.env, /phpmyadmin — их не существует на сервере, но запросы всё равно грузят Nginx и попадают в access.log. Готовый фильтр nginx-botsearch уже входит в поставку Fail2ban и ищет частые 404/403 от одного IP.

sudo nano /etc/fail2ban/jail.local

Добавляем секцию:

[nginx-botsearch]
enabled  = true
port    = http,https
filter  = nginx-botsearch
logpath  = /var/log/nginx/access.log
maxretry = 10
findtime = 600
bantime  = 3600

Если нужен свой критерий — например банить за частые 403 на конкретной локации — можно написать собственный фильтр в /etc/fail2ban/filter.d/nginx-403.conf:

[Definition]
failregex = ^<HOST> -.*"(GET|POST).*" 403

И включаем его в jail.local так же, как nginx-botsearch, указав filter = nginx-403.

Свой фильтр по логам приложения

Если веб-приложение ведёт собственный лог авторизации, Fail2ban можно натравить прямо на него — не обязательно ограничиваться системными логами. Допустим, приложение при неудачном входе пишет в /var/www/app/logs/auth.log строку вида:

2026-07-16 10:22:41 Invalid login attempt from 203.0.113.7 for user admin

Создаём фильтр:

sudo nano /etc/fail2ban/filter.d/app-auth.conf
[Definition]
failregex = ^.*Invalid login attempt from <HOST>
ignoreregex =

И джейл в jail.local:

[app-auth]
enabled  = true
filter  = app-auth
logpath  = /var/www/app/logs/auth.log
maxretry = 5
findtime = 600
bantime  = 1800
action    = iptables-allports
Перед тем как доверять новому фильтру, обязательно проверьте его на реальных строках лога: fail2ban-regex /var/www/app/logs/auth.log /etc/fail2ban/filter.d/app-auth.conf. Слишком узкий regex не забанит вообще никого, а слишком широкий может зацепить чужие IP из строк, где адрес встречается не как источник атаки — например из заголовка X-Forwarded-For внутри чужого запроса. Ошибка в обе стороны обесценивает джейл или превращает его в оружие против случайных пользователей.

Время бана и рецидивисты

Три базовых параметра управляют строгостью джейла:

maxretry = 5    # сколько попыток допустимо
findtime = 600    # за какой промежуток времени (секунды)
bantime  = 1800    # на сколько банить (секунды)

То есть 5 попыток за 10 минут — бан на 30 минут. Для тех, кто возвращается снова и снова, есть эскалация — каждый повторный бан того же IP увеличивается:

bantime.increment = true
bantime.rndtime   = 30m
bantime.maxtime   = 1w
bantime.factor    = 2

bantime.factor умножает срок бана при каждом повторном нарушении (30 минут → час → два часа и так далее, до bantime.maxtime), а bantime.rndtime добавляет случайный разброс, чтобы бот не мог точно рассчитать момент снятия бана.

Просмотр и снятие бана

Посмотреть состояние конкретного джейла — сколько банов активно сейчас и какие IP:

sudo fail2ban-client status sshd

Список всех джейлов:

sudo fail2ban-client status

Снять бан с адреса — например, если под бан случайно попал ваш собственный IP:

sudo fail2ban-client set sshd unbanip 203.0.113.7

Если доступ по SSH уже потерян и подключиться нельзя, эту же команду выполняют через веб-консоль (VNC/serial console) в панели провайдера — она не зависит от сетевого доступа, который как раз заблокирован.

Оповещения о банах

Чтобы не заходить на сервер и не проверять fail2ban-client status вручную, можно настроить письмо при каждом бане. В jail.local в секции [DEFAULT]:

destemail = you@example.com
sender    = fail2ban@yourserver.ru
action     = %(action_mw)s

action_mw — встроенный шаблон «забанить + отправить письмо с whois-информацией по IP». Для этого на сервере должен быть настроен локальный MTA (например postfix) или внешний relay. Вместо почты можно дёрнуть webhook собственным скриптом — action допускает произвольную команду при бане и разбане, но для большинства VPS почтового оповещения достаточно.

Типовые ошибки

  • Фильтр написан, но не банит никого — regex не совпадает со строками реального лога; проверяйте через fail2ban-regex <лог> <фильтр> перед тем, как включать джейл в бою.
  • Правки в jail.local или filter.d/*.conf не применились — конфиг меняется на диске, а демон продолжает работать со старым, пока не выполнить sudo systemctl restart fail2ban.
  • Забанили сами себя и потеряли доступ по SSH — снимается через fail2ban-client unbanip с другого IP или через веб-консоль провайдера; на время отладки нового джейла полезно добавить свой рабочий IP в ignoreip.
  • Логи ротируются logrotate, путь меняется на .1 или архивируется — джейл продолжает следить за уже переименованным файлом и перестаёт видеть новые строки; после смены пути приложения проверяйте актуальность logpath.
  • Джейл включён, но забыли добавить порт или протокол в port = — action не сработает так, как ожидается, даже если бан формально зафиксирован в логе.

Что дальше

Fail2ban защищает от атак снаружи, но не менее важно не забывать о рутинном обслуживании сервера изнутри: автоматизируем рутинные задачи обслуживания сервера через cron →