Fail2ban на Ubuntu: защита SSH и сайта от брутфорса на VPS

Обучение · Linux / VPS / Безопасность

Любой VPS с белым IP-адресом начинают сканировать боты в первые минуты после запуска. Чаще всего это автоматический перебор паролей к SSH, а если на сервере есть сайт — то и перебор форм входа: /wp-login.php, /login, панели администратора. Один прочитанный auth.log быстро показывает картину: сотни попыток входа с чужих IP-адресов каждый день.

Fail2ban не заменяет ключи SSH и надёжные пароли, но снижает нагрузку от брутфорса и закрывает источник шума в логах: он следит за файлами логов, находит признаки неудачных попыток входа и на время блокирует IP-адрес через firewall. В этом руководстве настроим Fail2ban на Ubuntu для защиты SSH, разберём разницу между iptables и nftables при бане, добавим фильтр для формы входа на сайте за Nginx и покажем, как проверять и снимать баны, не заблокировав самого себя.

Как устроен Fail2ban

Fail2ban состоит из трёх связанных частей.

Где разместить проект

Для сайтов, ботов и pet-проектов можно использовать Beget: виртуальный хостинг от 420 ₽/мес., VPS от 330 ₽/мес. и тестовый период.

Перейти на Beget →

  • Filter — регулярное выражение, которое ищет в логе строку неудачной попытки входа. Для SSH это, например, строки вида Failed password for ... from <HOST> в /var/log/auth.log.
  • Jail — правило, которое связывает фильтр с логом, лимитами (maxretry, findtime) и действием при срабатывании.
  • Action — команда, которая реально блокирует адрес: добавляет правило в nftables/iptables, дёргает firewalld, либо просто отправляет письмо.

Демон fail2ban-server перечитывает логи, а fail2ban-client — консольная утилита для управления: статус jail’ов, ручной бан и разбан IP.

Что нужно перед установкой

  • VPS с Ubuntu и пользователем с правами sudo;
  • уже настроенный вход по SSH-ключу — Fail2ban дополняет эту защиту, а не заменяет её (если ключи ещё не настроены, сначала используйте руководство по безопасному SSH);
  • понимание, какой firewall-бэкенд использует сервер — iptables или nftables (это важно для шага с banaction, см. ниже);
  • отдельный SSH-сеанс на всякий случай: изменения firewall-правил не должны обрываться на середине.

Проверьте, что сейчас использует ядро для фильтрации:

sudo iptables -V
sudo nft --version
sudo update-alternatives --display iptables 2>/dev/null

На Ubuntu 20.04+ и Debian 11+ по умолчанию используется nftables, но пакет iptables может присутствовать как совместимая обвязка (iptables-nft). Это определяет, какой banaction понадобится Fail2ban дальше.

Устанавливаем Fail2ban

sudo apt update
sudo apt install fail2ban
sudo systemctl enable --now fail2ban
sudo systemctl status fail2ban --no-pager

Если сервис не стартовал, причину обычно видно сразу в журнале:

sudo journalctl -u fail2ban --no-pager -n 50

Создаём jail.local вместо правки jail.conf

Конфигурация читается в таком порядке, и каждый следующий файл переопределяет предыдущий:

/etc/fail2ban/jail.conf
/etc/fail2ban/jail.d/*.conf
/etc/fail2ban/jail.local
/etc/fail2ban/jail.d/*.local

jail.conf обновляется вместе с пакетом, поэтому правки в нём теряются при апдейте. Стандартный подход — скопировать его в jail.local и менять уже копию:

sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
sudo nano /etc/fail2ban/jail.local

В jail.local не обязательно оставлять все секции — достаточно указать только то, что вы хотите переопределить.

Настраиваем секцию [DEFAULT]

Основные параметры, которые стоит проверить в блоке [DEFAULT]:

[DEFAULT]
ignoreip = 127.0.0.1/8 ::1 203.0.113.10
bantime  = 1h
findtime = 10m
maxretry = 5
backend  = systemd
  • ignoreip — список адресов, которые никогда не банятся. Обязательно добавьте сюда свой текущий рабочий IP, иначе рискуете забанить самого себя после нескольких опечаток в пароле.
  • bantime — на сколько блокируется адрес. По умолчанию 10 минут; для боевого сервера обычно ставят от часа и выше. Отрицательное значение (bantime = -1) означает бан навсегда.
  • findtime — окно, в котором считаются неудачные попытки.
  • maxretry — сколько попыток допустимо в пределах findtime, прежде чем сработает бан.
  • backend = systemd — использовать journald вместо чтения текстового файла лога; актуально, если rsyslog не пишет /var/log/auth.log в вашем образе Ubuntu. Если файл лога на месте, можно оставить backend = auto.

После правки конфигурации перезапустите сервис и проверьте, что синтаксис верный:

sudo fail2ban-client -d          # тест конфигурации без применения
sudo systemctl restart fail2ban
sudo fail2ban-client status

Включаем защиту SSH

В jail.local найдите или добавьте секцию [sshd]:

[sshd]
enabled  = true
port     = ssh
filter   = sshd
logpath  = %(sshd_log)s
backend  = %(sshd_backend)s
maxretry = 4
findtime = 10m
bantime  = 1h

Если SSH слушает нестандартный порт, замените port = ssh на конкретный номер — иначе Fail2ban будет банить IP в firewall, но правило будет применяться не к тому порту, который реально атакуют.

Примените изменения и проверьте статус jail’а:

sudo systemctl restart fail2ban
sudo fail2ban-client status sshd

Вывод должен показать, сколько попыток зафиксировано и сколько адресов забанено:

Status for the jail: sshd
|- Filter
|  |- Currently failed: 2
|  |- Total failed:     37
|  `- Journal matches:  ...
`- Actions
   |- Currently banned: 1
   |- Total banned:     6
   `- Banned IP list:   203.0.113.42

nftables или iptables: от этого зависит banaction

Здесь чаще всего ошибаются: Fail2ban по умолчанию рассчитан на iptables-действия (banaction = iptables-multiport), но современные Ubuntu и Debian используют nftables как основной бэкенд. Если оставить действие по умолчанию на системе, где нет классического iptables, попытка бана в логе Fail2ban может завершиться ошибкой действия, а сам бан — не примениться.

Fail2ban поставляется со встроенными nftables-действиями:

  • nftables (алиас nftables-multiport) — банит по конкретным портам, использует nftables set для хранения адресов;
  • nftables-allports — банит адрес полностью, по всем портам.

Чтобы переключиться на nftables, в [DEFAULT] укажите:

[DEFAULT]
banaction = nftables-multiport
banaction_allports = nftables-allports

После смены banaction перезапустите Fail2ban и проверьте, что таблица и набор адресов реально созданы:

sudo systemctl restart fail2ban
sudo nft list tables
sudo nft list set inet f2b-table addr-set-sshd

Если сервер работает через firewalld (характерно для RHEL/Rocky/Fedora, реже — для управляемых Ubuntu-образов), вместо nftables-действий обычно используют firewallcmd-multiport и firewallcmd-allports — уточняйте активный бэкенд командой firewall-cmd --state, если пакет установлен.

Проверяем фильтр до того, как он начнёт банить

Прежде чем полагаться на новый или изменённый фильтр, прогоните его по реальному логу через fail2ban-regex — это безопасный тест без применения бана:

sudo fail2ban-regex /var/log/auth.log /etc/fail2ban/filter.d/sshd.conf

В выводе fail2ban-regex покажет число совпадений и несколько примеров строк. Если совпадений ноль, а атаки в логе точно есть, проблема обычно в logpath или в backend — например, дистрибутив пишет логи только в journald, а logpath указывает на пустой файл.

Защищаем форму входа на сайте за Nginx

Fail2ban одинаково хорошо работает с логами веб-сервера, если запросы там различимы. Частый сценарий — перебор /wp-login.php или собственной формы /login. Сначала убедитесь, что Nginx вообще фиксирует такие запросы в отдельном access-логе — так фильтру проще, а Fail2ban не нужно парсить общий лог всех сайтов на сервере.

Создайте фильтр /etc/fail2ban/filter.d/nginx-login.conf:

[Definition]
failregex = ^<HOST> -.*"(POST|GET) /(wp-login\.php|login)(/)?.*" (200|401|403)
ignoreregex =

И jail в jail.local:

[nginx-login]
enabled  = true
filter   = nginx-login
port     = http,https
logpath  = /var/log/nginx/example.com.access.log
maxretry = 5
findtime = 10m
bantime  = 1h
banaction = nftables-multiport

Код ответа 200 в фильтре может показаться странным, но именно так часто выглядит неудачная попытка входа в WordPress и многих самописных формах — сервер отвечает 200 OK с HTML-страницей ошибки, а не HTTP-кодом ошибки. Проверьте это на своём сайте: посмотрите реальные строки лога для неудачного логина и подстройте failregex под фактический формат, прежде чем включать jail в бой. Правило «одна попытка — одна строка лога» работает не для всех фреймворков: некоторые логируют только успешный вход, и тогда придётся смотреть на уровень приложения, а не Nginx.

Проверка фильтра на реальном логе — тот же принцип, что и для SSH:

sudo fail2ban-regex /var/log/nginx/example.com.access.log /etc/fail2ban/filter.d/nginx-login.conf
sudo systemctl restart fail2ban
sudo fail2ban-client status nginx-login

Если сайт уже стоит за reverse proxy, убедитесь, что в access-логе Nginx пишется реальный IP клиента, а не адрес прокси перед ним — иначе Fail2ban будет банить не того, кого нужно. Логика передачи X-Forwarded-For и настройка доверенных прокси разобраны в статье про Nginx reverse proxy для Docker-приложения.

Ручной бан, разбан и просмотр списка

Иногда бан нужно снять вручную — например, если под фильтр попал легитимный адрес:

sudo fail2ban-client set sshd unbanip 203.0.113.42
sudo fail2ban-client set sshd banip 198.51.100.7
sudo fail2ban-client status sshd

Список всех активных jail’ов:

sudo fail2ban-client status

Если нужно временно отключить jail целиком, не удаляя конфигурацию:

sudo fail2ban-client stop

или для одного jail — установить enabled = false в jail.local и перезапустить сервис.

Диагностика: если бан не срабатывает

  1. Проверьте, что jail включён и видит попытки.

    sudo fail2ban-client status sshd

    Если Currently failed не растёт при заведомо неудачных попытках входа — проблема в logpath/backend, а не в действии бана.

  2. Проверьте сам лог Fail2ban.

    sudo tail -n 100 /var/log/fail2ban.log

    Строки NOTICE [sshd] Ban 203.0.113.42 подтверждают, что бан выполнен на уровне Fail2ban. Если такой строки нет — не дошло до действия, смотрите фильтр.

  3. Проверьте, что правило реально появилось в firewall.

    sudo nft list set inet f2b-table addr-set-sshd
    # или для iptables-варианта:
    sudo iptables -L f2b-sshd -n

    Если в логе Fail2ban есть Ban, а в firewall правила нет — почти всегда это несовпадение banaction с реальным бэкендом (iptables-действие на системе с nftables, или наоборот).

  4. Не тестируйте бан на своём единственном SSH-сеансе. Оставляйте одно открытое соединение и проверяйте maxretry из другого источника — второй SSH-сессии, VPN или мобильного интернета.

Чек-лист перед тем, как доверять Fail2ban

  • в ignoreip добавлен ваш рабочий IP-адрес;
  • banaction/banaction_allports соответствуют реальному firewall-бэкенду сервера (nftables или iptables);
  • фильтр SSH проверен через fail2ban-regex на живом логе;
  • для веб-фильтров есть отдельный access-лог сайта и подтверждено, что в нём виден реальный IP клиента;
  • fail2ban-client status <jail> показывает растущий счётчик failed при тестовых неудачных попытках;
  • после бана правило действительно видно в nft list set или iptables -L;
  • bantime, findtime, maxretry осознанно выбраны под конкретный сервис, а не оставлены по умолчанию вслепую;
  • есть способ зайти на сервер помимо основного SSH-сеанса, если что-то пойдёт не так.

FAQ

Fail2ban защищает от DDoS?

Нет. Fail2ban реагирует на записи в логах аутентификации и по своей природе рассчитан на брутфорс входа, а не на объёмные атаки на сетевом уровне. От DDoS нужны другие механизмы — на уровне провайдера, CDN или отдельного firewall перед сервером.

Можно ли использовать Fail2ban вместе с UFW?

Да. UFW сам по себе — обвязка над iptables/nftables и не мешает Fail2ban добавлять свои правила бана. Убедитесь только, что banaction соответствует тому бэкенду, который реально активен (см. раздел про nftables и iptables выше).

Почему в логе Fail2ban есть «Ban», а подключение с забаненного IP всё равно проходит?

Чаще всего либо banaction не совпадает с активным firewall-бэкендом, либо правило добавляется в цепочку/таблицу, которая не подключена к реальному пути пакета — например, кастомный nftables-конфиг перекрывает таблицу Fail2ban собственными правилами с более высоким приоритетом. Проверяйте nft list ruleset целиком, а не только таблицу Fail2ban.

Что лучше — растущий bantime или фиксированный?

Для интерактивных сервисов вроде SSH многие используют bantime.increment = true, чтобы повторные нарушения банились на всё более долгий срок. Это снижает эффект от адресов, которые ждут окончания короткого бана и пробуют снова. Настройка описана в официальной документации jail.conf.

Официальная документация

Нужна помощь с настройкой firewall и безопасности VPS?

Проверим SSH, firewall, Fail2ban, обновления и журналы сервера. Аудит — от 9 900 ₽.

Узнать состав и стоимость →

Бесплатное руководство «Безопасный Linux/VPS»

14 практических страниц: SSH, firewall, обновления, резервные копии, WordPress, Docker и действия при признаках взлома.

Получить PDF →

Хотите системно изучить программирование?

Подборка онлайн-курсов для разработчиков — Python, Java, веб и DevOps.

Смотреть курсы →

Бесплатные инструменты

Калькулятор подсетей · SSH-конфиг · Nginx · Docker Compose

Бесплатное руководство «Безопасный Linux/VPS»

14 практических страниц: SSH, firewall, обновления, резервные копии, WordPress, Docker и действия при признаках взлома.

Получить PDF →

Нужен аудит или безопасная настройка Linux/VPS?

Проверим SSH, firewall, обновления, резервные копии, Docker и журналы. Аудит — от 9 900 ₽.

Узнать состав и стоимость → 8 918 532 81 08

Статья может содержать партнёрские ссылки. Это не влияет на стоимость для вас, но помогает развивать блог.