Fail2ban давно стал базовым инструментом для защиты публичных сервисов от перебора паролей и шумных ботов. Но если несколько лет назад большинство примеров строилось вокруг текстовых логов и iptables, то на современных Debian и Ubuntu картина изменилась: события удобнее читать через journald, а блокировки логично передавать в nftables. Поэтому типичный вопрос сегодня звучит так: как собрать всё вместе так, чтобы конфигурация была не разовой, а нормально сопровождалась в продакшене.
Ниже разберём связку fail2ban + nftables + journald на Debian и Ubuntu. Фокус будет на sshd, потому что это самый частый и самый полезный сценарий. Заодно посмотрим, как оформить jail.local, какой banaction выбирать, когда включать recidive и как разбирать ситуацию, когда Fail2ban формально работает, но банов нет.
Подход максимально практичный: без лишней теории, но с пониманием, почему отдельные настройки лучше оставить именно такими. У Fail2ban типичная проблема не в том, что он не запускается, а в том, что он запускается и молча не ловит нужные события, не добавляет правила в firewall или банит слишком агрессивно.
Когда связка Fail2ban, journald и nftables особенно уместна
Если у вас свежий Debian или Ubuntu, сама система уже подталкивает к такой архитектуре. systemd-journald собирает логи сервисов централизованно, а nftables стал основным сетевым фильтром вместо старого набора интерфейсов вокруг iptables. Поэтому использовать Fail2ban с backend = systemd и действиями через nftables — это уже не экзотика, а нормальная современная схема.
Особенно она удобна в трёх случаях: когда sshd пишет в journal, когда у вас уже есть собственный ruleset в nftables и вы не хотите смешивать его с устаревшими iptables-обвязками, и когда сервер минимальный по составу пакетов и файловые логи живут не там, где вы ожидаете.
Если сервер новый, лучше сразу строить конфигурацию на journald и nftables, чем потом переделывать рабочую, но уже устаревшую схему с file backend и iptables-действиями.
При этом Fail2ban не заменяет базовую защиту SSH. Он снижает шум и обрубает массовый перебор, но не отменяет необходимость использовать ключи, ограничивать доступ по адресам, обновлять OpenSSH и по возможности убирать парольный вход. Если хотите дополнительно усилить SSH, посмотрите практическое руководство по защите VDS: SSH и firewall.
Что установить и проверить перед настройкой
На Debian и Ubuntu обычно достаточно пакетов fail2ban и nftables. Но перед правкой конфигурации стоит проверить три вещи: что SSH действительно пишет в journald, что nftables установлен и активен, и что Fail2ban видит нужные action-файлы для работы с nftables.
Базовая последовательность проверки выглядит так:
sudo apt update
sudo apt install -y fail2ban nftables
sudo systemctl enable --now nftables
sudo systemctl enable --now fail2ban
sudo systemctl status ssh
sudo journalctl -u ssh --no-pager | tail -n 50
На Debian и Ubuntu unit нередко называется ssh, а не sshd. Это нормально. В журнале должны быть видны записи об успешных и неуспешных попытках входа. Если журнал пустой, начинать с Fail2ban рано: сначала нужно понять, куда вообще пишет OpenSSH.
Дальше полезно посмотреть версию Fail2ban и доступные действия:
fail2ban-client -V
ls /etc/fail2ban/action.d | grep nftables
ls /etc/fail2ban/filter.d | grep ssh
Обычно вы увидите файлы вроде nftables.conf и стандартный фильтр sshd.conf. Названия могут слегка отличаться в зависимости от версии пакета, поэтому лучше смотреть на реальное содержимое системы, а не копировать конфиг из случайной заметки без проверки.
Почему не стоит редактировать jail.conf
Одна из самых частых ошибок — править /etc/fail2ban/jail.conf напрямую. Формально это работает, но сильно усложняет обновления и поддержку. После обновления пакета становится трудно понять, где исходный дефолт, а где ваши изменения.
Нормальная практика — оставить jail.conf в покое и создать свой jail.local. Туда выносится общая логика и все локальные jail. Такой подход проще для сопровождения и удобнее для сравнения после апдейтов.
Минимальный рабочий шаблон для старта:
[DEFAULT]
banaction = nftables
backend = systemd
bantime = 1h
findtime = 10m
maxretry = 5
usedns = warn
[sshd]
enabled = true
port = ssh
filter = sshd
journalmatch = _SYSTEMD_UNIT=ssh.service
logpath = %(sshd_log)s
maxretry = 5
findtime = 10m
bantime = 1h
Здесь важно несколько моментов. backend = systemd говорит Fail2ban читать события не из файлового лога, а из journal. banaction = nftables заставляет его использовать блокировки через nftables. А строка journalmatch ограничивает выборку конкретным unit, чтобы Fail2ban не просматривал лишние записи.
logpath при systemd backend уже не играет главную роль, но его часто оставляют для совместимости шаблонов. Критично здесь именно корректное значение journalmatch.

Как понять правильное значение journalmatch
Это ключевой этап, потому что именно из-за него чаще всего не срабатывает jail для sshd. На Debian и Ubuntu имя unit обычно ssh.service, а не sshd.service. Если указать неправильное имя, Fail2ban спокойно запустится, но нужные события не увидит.
Проверять лучше так:
systemctl status ssh
journalctl -u ssh --no-pager | tail -n 20
journalctl _SYSTEMD_UNIT=ssh.service --no-pager | tail -n 20
Если последний запрос показывает события авторизации, значит строка journalmatch = _SYSTEMD_UNIT=ssh.service подходит. На кастомных установках имя unit может отличаться, и это нужно подтвердить фактическими данными.
Разбираем ключевые параметры jail.local
Чтобы настройка не превратилась в магию, полезно понимать смысл базовых параметров.
bantime— на сколько блокировать адрес после срабатывания jail.findtime— окно времени, в пределах которого считаются неудачные попытки.maxretry— сколько неудачных попыток допускается до бана.banaction— каким способом Fail2ban добавляет блокировку в firewall.backend— откуда читаются события: из файлов, journal и так далее.usedns— нужно ли делать DNS-резолв при анализе логов.
Для sshd на публичном сервере нормальный старт — maxretry = 5, findtime = 10m, bantime = 1h. Это не слишком мягко и не слишком жёстко. Если у вас команда с нестабильным доступом, VPN и часто меняющимися адресами, можно немного увеличить maxretry или сократить bantime.
Если же SSH открыт всему интернету и парольный вход всё ещё разрешён, правильнее сначала пересмотреть модель доступа, а уже потом ужесточать Fail2ban. Для совсем простых проектов и панелей управления часть задач удобно решается на виртуальном хостинге, а для полного контроля над SSH и firewall уже нужен VDS.
Выбор banaction для nftables
В простом случае достаточно banaction = nftables. Fail2ban сам создаст нужные цепочки или наборы и будет добавлять туда адреса для блокировки. На современных Debian и Ubuntu это обычно самый удобный вариант.
Но есть практический нюанс: если у вас уже есть сложный ruleset nftables, стоит заранее посмотреть, как именно действие Fail2ban в него встраивается. Не потому, что конфликт обязателен, а потому, что порядок обработки пакетов и структура таблиц действительно важны. Если firewall у вас уже сильно кастомизирован, сначала изучите шаблон в /etc/fail2ban/action.d/, а потом включайте это в бою.
После применения настроек перезапустите сервис и проверьте, что jail создались:
sudo systemctl restart fail2ban
sudo fail2ban-client status
sudo fail2ban-client status sshd
Если jail активен, но банов пока нет, это нормально. Следующий шаг — убедиться, что при срабатывании действительно меняется состояние nftables.
Для этого смотрят текущий набор правил:
sudo nft list ruleset
При активном бане в ruleset появятся элементы, созданные действием Fail2ban. Их точный вид зависит от версии action и пакета, поэтому ориентируйтесь не на скриншоты из интернета, а на сам факт появления сущностей, связанных с Fail2ban.
Как проверить, что sshd действительно ловится фильтром
Если конфигурация выглядит верной, а банов всё равно нет, не начинайте сразу менять лимиты или переписывать фильтр. Сначала проверьте три слоя отдельно: есть ли нужные сообщения в journald, совпадает ли journalmatch, и распознаёт ли фильтр sshd эти события как неудачные попытки входа.
Для первичной диагностики помогают такие команды:
sudo fail2ban-client status sshd
sudo fail2ban-client get sshd logpath
sudo fail2ban-client get sshd journalmatch
sudo fail2ban-regex systemd-journal /etc/fail2ban/filter.d/sshd.conf
Команда с fail2ban-regex особенно полезна: она показывает, совпадает ли стандартный фильтр с реальными сообщениями из journal. Если совпадений нет, проблема не в firewall, а во входных данных или в несоответствии формата сообщений.
Главное правило отладки Fail2ban: сначала докажите, что событие найдено фильтром, и только потом проверяйте бан в firewall. Иначе очень легко лечить не тот слой.
Иногда проблема упирается в сам сценарий теста. OpenSSH может писать разные формулировки для неверного пароля, несуществующего пользователя, отключённого метода аутентификации или отказа по политике доступа. Стандартный фильтр sshd покрывает основные случаи, но нестандартную схему аутентификации лучше валидировать отдельно.
Если вам нужна дополнительная видимость по входам и отказам, полезно также настроить оповещения о входах SSH через PAM и journald.
Настройка recidive: банить рецидивистов дольше
Jail recidive нужен для адресов, которые попадаются снова и снова. Идея простая: обычный jail sshd банит на час, а если один и тот же адрес регулярно возвращается, отдельный jail может выдать уже долгую блокировку, например на неделю.
Это удобно, потому что массовые сканеры работают волнами. Краткосрочный бан сбивает текущую активность, но через некоторое время адрес снова начинает стучаться. recidive позволяет не делать основной jail слишком жёстким и при этом сильнее наказывать повторяющиеся источники.
Пример настройки:
[recidive]
enabled = true
backend = auto
banaction = nftables
logpath = /var/log/fail2ban.log
bantime = 1w
findtime = 1d
maxretry = 5
Тут есть важная тонкость: recidive обычно анализирует лог самого Fail2ban, а не journal OpenSSH напрямую. Поэтому нужно убедиться, что ведётся файл /var/log/fail2ban.log. Если этого файла нет или он пустой, jail будет формально активным, но практической пользы не даст.
Поэтому после включения recidive проверьте не только статус jail, но и наличие реальных записей о банах в логах Fail2ban.

Практичный пример итогового jail.local
Ниже конфигурация, с которой удобно стартовать на большинстве серверов Debian и Ubuntu, где SSH — главный публичный сервис:
[DEFAULT]
ignoreip = 127.0.0.1/8 ::1
backend = systemd
banaction = nftables
bantime = 1h
findtime = 10m
maxretry = 5
usedns = warn
[sshd]
enabled = true
port = ssh
filter = sshd
journalmatch = _SYSTEMD_UNIT=ssh.service
maxretry = 5
findtime = 10m
bantime = 1h
[recidive]
enabled = true
backend = auto
banaction = nftables
logpath = /var/log/fail2ban.log
bantime = 1w
findtime = 1d
maxretry = 5
Обратите внимание на ignoreip. Если у вас есть фиксированные административные адреса, их стоит добавить сразу, чтобы не забанить себя при неудачных входах. Но не нужно без необходимости добавлять туда крупные подсети целых провайдеров.
Если сервер используется под проекты, CI, staging или административный доступ, такая защита особенно полезна: SSH начинают сканировать почти сразу после появления публичного адреса в интернете.
Типичные ошибки на Debian и Ubuntu
Хотя сама схема довольно простая, на практике чаще всего встречаются одни и те же проблемы.
Перепутаны unit ssh и sshd
Классическая ошибка: в journalmatch указывают sshd.service, а на системе реально существует ssh.service. В итоге jail активен, но пустой. Проверяется это одной командой systemctl status ssh.
Выбран не тот backend
Если оставить файловый backend, а нужные сообщения живут только в journald, Fail2ban не увидит события. Поэтому при такой схеме лучше явно задавать backend = systemd, а не надеяться на автоопределение.
banaction не соответствует установленным action
В сети много примеров со старыми или дистрибутивоспецифичными именами действий. Перед настройкой всегда смотрите, какие файлы реально лежат в /etc/fail2ban/action.d/.
Ожидание, что recidive заработает без fail2ban.log
Если не ведётся лог самого Fail2ban в файл, jail recidive часто оказывается формально включённым, но бесполезным. Важна не только запись в конфиге, но и реальный поток событий в логе.
Слишком агрессивные лимиты
maxretry = 2 и очень длинный bantime красиво выглядят в теории, но на практике легко приводят к самобану команды, особенно при VPN, смене адресов и нескольких параллельных автоматизациях. Лучше начать с умеренных значений и потом ужесточать их по реальным данным.
Как безопасно тестировать баны
Тестировать лучше с отдельного адреса или при наличии консольного доступа, чтобы не закрыть себе дверь на сервер. Хороший сценарий — оставить одну рабочую SSH-сессию активной, а с другой машины намеренно сделать несколько неудачных попыток входа.
После этого проверьте:
sudo fail2ban-client status sshd
sudo fail2ban-client status recidive
sudo nft list ruleset
sudo journalctl -u fail2ban --no-pager | tail -n 50
Если адрес попал в список banned, а в ruleset появился соответствующий бан, значит связка работает. Снять бан вручную можно так:
sudo fail2ban-client set sshd unbanip 203.0.113.10
Подставьте сюда свой тестовый адрес. И главное — не проводите такие проверки с production-адреса без запасного доступа.
Что оставить как рабочий минимум
Если нужна краткая практическая рекомендация, то для Debian и Ubuntu разумный базовый вариант сегодня такой: использовать backend = systemd, задавать корректный journalmatch для SSH, включать banaction = nftables и поверх основного jail держать recidive для повторных нарушителей.
Такая схема хорошо укладывается в современный стек системы, не требует разбирать текстовые логи там, где уже есть journald, и не тянет за собой старую firewall-модель на iptables. При этом она остаётся прозрачной для диагностики: отдельно виден журнал событий, отдельно статус Fail2ban и отдельно итоговые правила в nftables.
Если вы поднимаете SSH и веб-сервисы на отдельном сервере, не откладывайте такую настройку на потом. Fail2ban не решает все вопросы безопасности, но как часть базовой гигиены сервера работает отлично — особенно когда настроен осознанно, а не собран из фрагментов чужих конфигов.


