Fail2ban читает логи сервера, замечает адреса, которые раз за разом ошибаются с паролем, и на время закрывает им доступ через файрвол. Сам пароль он сильнее не делает: слабый пароль остаётся слабым, просто подбирать его становится долго и невыгодно. Зато из логов исчезают тысячи мусорных попыток в сутки, и на сервере снова видно то, что действительно важно.
Сначала рабочая настройка для SSH, которую можно поставить за десять минут на Ubuntu, Debian или AlmaLinux. Дальше разберём, почему она именно такая, как прикрутить защиту входа в WordPress, что делать с Docker и Cloudflare и как не забанить самого себя.
Быстрая настройка: SSH под защитой за десять минут
На Ubuntu и Debian пакет лежит в стандартном репозитории:
sudo apt update sudo apt install fail2ban
На AlmaLinux и Rocky Linux его берут из репозитория EPEL. Баны он там ставит через firewalld, поэтому файрвол должен быть запущен:
sudo dnf install epel-release sudo dnf install fail2ban sudo systemctl enable --now firewalld fail2ban
Теперь свой файл настроек. Файл /etc/fail2ban/jail.conf не трогай: в нём значения по умолчанию, и пакет перезапишет его при обновлении. Свои правки кладут в отдельный файл в папке jail.d:
sudo nano /etc/fail2ban/jail.d/custom.local
Впиши в него это, строка в строку, без точек с запятой в конце:
[DEFAULT] bantime = 1h findtime = 10m maxretry = 5 bantime.increment = true bantime.maxtime = 1w ignoreip = 127.0.0.1/8 ::1 backend = auto [sshd] enabled = true backend = systemd port = ssh
Если SSH у тебя переехал на другой порт, в последней строке вместо ssh поставь номер, например port = 2222. Про эту строку забывают чаще, чем про любую другую. Fail2ban при этом честно находит атакующих и так же честно закрывает им 22-й порт, на котором никого нет, а подбор на 2222-м продолжается как ни в чём не бывало.
Сохрани файл, проверь конфигурацию и перезапусти службу:
sudo fail2ban-client -t sudo systemctl restart fail2ban sudo fail2ban-client status sshd
Первая команда при правильном конфиге пишет OK: configuration test is successful. Если там ошибка, в ней будет название файла и параметра, и перезапускать службу до исправления нет смысла. Третья команда показывает состояние защиты SSH:
Status for the jail: sshd |- Filter | |- Currently failed: 3 | |- Total failed: 41 | `- Journal matches: _SYSTEMD_UNIT=ssh.service + _COMM=sshd `- Actions |- Currently banned: 2 |- Total banned: 2 `- Banned IP list: 198.51.100.23 203.0.113.77
Имя службы в строке Journal matches отличается от системы к системе (ssh.service или sshd.service), это нормально. Смотреть нужно на счётчики. На сервере с открытым 22-м портом первые чужие попытки входа обычно появляются в течение часа после установки, а часто и в первые минуты. Если через пару часов в Total failed по-прежнему ноль, что-то не так, и следующий раздел как раз про это.
Почему именно так: журнал, backend и точка с запятой
Fail2ban сам ничего не видит, он читает то, что пишут другие программы. Раньше SSH-сервер писал о неудачных входах в файл /var/log/auth.log (на AlmaLinux – /var/log/secure), и Fail2ban по умолчанию ищет именно его. Но в современных системах записи сначала попадают в системный журнал journald, а в файл их перекладывает отдельная служба rsyslog. На минимальных образах Debian 12, которые ставят многие провайдеры, rsyslog нет, файла нет, и Fail2ban падает при запуске с такой строкой:
ERROR Failed during configuration: Have not found any log file for sshd jail
Строка backend = systemd в секции [sshd] говорит ему читать журнал напрямую. На Debian 12 и на серверах, собранных кем-то вручную, именно она избавляет от ошибки выше. Если на AlmaLinux служба ругается на отсутствующий модуль systemd, доставь пакет python3-systemd.
А вот строка backend = auto в [DEFAULT] выглядит лишней, и на первый взгляд так и есть. Но в Ubuntu 24.04 сопровождающие пакета положили в файл /etc/fail2ban/jail.d/defaults-debian.conf backend = systemd сразу для всех защит, а не только для SSH. Для SSH это удобно. Для всего остального это ловушка: логи nginx и Apache лежат в обычных файлах, а защита сайта, которую ты включишь позже, унаследует чтение журнала. Ошибки при этом не будет ни одной. Защита запустится, будет годами смотреть в журнал, где записей nginx нет, и показывать Total failed: 0. Сами разработчики Fail2ban как раз по этой причине убрали журнал из общих настроек для Debian: такую поломку почти невозможно заметить. Строка backend = auto возвращает чтение файлов для всех защит, а журнал остаётся только там, где он нужен. Посмотреть, что прописано у тебя, можно командой cat /etc/fail2ban/jail.d/defaults-debian.conf.
Теперь про точку с запятой. В файлах Fail2ban знак ; после пробела начинает комментарий, а без пробела становится частью значения. Строка maxretry = 5; превращается в число «5;», которого не бывает, а logpath = /var/log/auth.log; – в путь к несуществующему файлу. В интернете гуляет много инструкций, где конфиги набраны в текстовом редакторе блога и получили «;» в конце каждой строки. Скопированные оттуда, они валят службу при запуске. Команда fail2ban-client -t ловит такие ошибки до перезапуска, поэтому её стоит запускать после каждой правки.
И последнее, про расширение .local. Fail2ban читает настройки слоями: сначала jail.conf, потом файлы jail.d/*.conf по алфавиту, потом jail.local и в конце jail.d/*.local. Если один параметр встречается несколько раз, работает последнее значение. Дистрибутивный defaults-debian.conf имеет расширение .conf, а твой custom.local читается после него, так что твои значения гарантированно побеждают.
Сколько банить: maxretry, findtime и бан, который растёт
Три числа работают вместе. Если с одного адреса за findtime (у нас 10 минут) пришло maxretry неудачных попыток (у нас 5), адрес получает бан на bantime. Пять ошибок за десять минут человек, который забыл пароль, наберёт вряд ли, а бот делает это за пару секунд.
По умолчанию bantime всего 10 минут. На удивление, для ботов это почти ничего не меняет: они не устают и не обижаются, через десять минут адрес снова в деле, и за сутки он успевает перебрать почти столько же паролей, сколько без всякой защиты. Поэтому в быстрой настройке стоит час и bantime.increment = true. С этой опцией каждый следующий бан того же адреса вдвое длиннее предыдущего: час, два, четыре, восемь и так далее, пока не упрётся в bantime.maxtime, у нас это неделя. Человек, который один раз ошибся, отделается часом. Бот, который возвращается снова и снова, через несколько кругов сидит в бане неделю.
Есть одна тонкость, про которую в инструкциях обычно молчат. Историю банов Fail2ban хранит в базе /var/lib/fail2ban/fail2ban.sqlite3 и чистит из неё записи старше суток. Бот, который вернулся через два дня, для Fail2ban снова новичок и начинает с часа. Чтобы память была длиннее, создай файл /etc/fail2ban/fail2ban.local:
[DEFAULT] dbpurgeage = 30d
База от этого подрастёт, но на обычном VPS речь о мегабайтах.
Для самых упорных есть отдельная защита recidive. Она читает собственный лог Fail2ban и, если адрес попадал в бан слишком часто, закрывает ему все порты сразу, а не только SSH. Включается двумя строками в том же custom.local:
[recidive] enabled = true
По умолчанию она банит на неделю тех, кого за сутки забанили пять раз. С растущим баном это скорее второй рубеж. Её плюс в другом: адрес, который долбился в SSH, не пойдёт следом подбирать пароль к почте или к админке сайта.
С maxretry для сайта всё сложнее, чем для SSH. К SSH подключаешься ты и, может, пара коллег, а к сайту приходят все подряд. Мобильные операторы часто выпускают в интернет сотни абонентов через один адрес, и на него же нередко приходится какой-нибудь заражённый телефон. Бан такого адреса на неделю отрезает от сайта заодно и живых посетителей. Для защиты сайта лучше короткий бан без роста или хотя бы с потолком в пару часов.
Если пароли в SSH уже выключены
Если вход по паролю отключён и работает только вход по ключу, подобрать доступ к SSH ботам не светит, сколько бы попыток они ни сделали. Fail2ban в этом случае защищает не доступ, а покой: логи становятся чище, а сервер меньше тратит процессор на рукопожатия с ботами. Для слабого тарифа на одно ядро последнее бывает заметно.
Вдобавок в OpenSSH начиная с версии 9.8 есть встроенный механизм PerSourcePenalties, включённый по умолчанию. SSH-сервер сам на время отказывает адресам, которые раз за разом не проходят проверку, без всякого Fail2ban. Версию покажет ssh -V. В Debian 13 она уже десятая, в Ubuntu 24.04 ещё 9.6, так что там механизма нет. Выходит, что для SSH Fail2ban становится вторым слоем, и основная польза от него смещается на сайт и почту. Про остальные меры для SSH, от запрета входа под root до доступа только с определённых адресов, отдельная статья: как защитить SSH.
Если хочешь, чтобы Fail2ban ловил и ботов, которые при выключенных паролях даже не доходят до попытки входа, добавь в секцию [sshd] строку mode = aggressive. Фильтр начнёт учитывать и оборванные соединения.
Защита входа в WordPress
Ради этого Fail2ban на VPS чаще всего и ставят. Перебор паролей к wp-login.php идёт на любом сайте на WordPress, даже никому не известном, ведь боты ищут сайты не по популярности, а по адресам подряд. И вот здесь готовые фильтры Fail2ban не помогают. Фильтр nginx-http-auth из стандартного набора ловит только ошибки HTTP-авторизации, то есть окошко браузера с логином и паролем. Форма входа WordPress – обычная страница, nginx в своём журнале ошибок её не отмечает, и такой фильтр ничего не найдёт.
Нужен свой фильтр по журналу запросов. Подсказку даёт сам WordPress. После неудачного входа он заново показывает форму с ошибкой и отвечает кодом 200, а после удачного перенаправляет в админку с кодом 302. Значит, считать нужно только POST-запросы к wp-login.php, на которые сервер ответил 200. Строка неудачной попытки в логе nginx выглядит так:
203.0.113.45 - - [29/Sep/2026:06:12:31 +0300] "POST /wp-login.php HTTP/1.1" 200 5387 "-" "Mozilla/5.0"
Создай файл фильтра:
sudo nano /etc/fail2ban/filter.d/wordpress-login.conf
[Definition] failregex = ^<HOST> -[^"]*"POST /wp-login\.php[^"]*" 200 ignoreregex =
<HOST> здесь пишется буквально, в угловых скобках: на это место Fail2ban сам подставит шаблон IP-адреса. Прежде чем включать фильтр, проверь его на настоящем логе:
sudo fail2ban-regex /var/log/nginx/access.log /etc/fail2ban/filter.d/wordpress-login.conf
В выводе ищи строку Lines: ... matched. Если совпадений ноль, а боты в логе есть, скорее всего, у nginx нестандартный формат журнала. Тогда регулярное выражение правят по реальной строке из лога. Когда совпадения есть, добавь в custom.local ещё одну секцию:
[wordpress-login] enabled = true filter = wordpress-login logpath = /var/log/nginx/access.log port = http,https maxretry = 6 bantime = 30m bantime.increment = false
Бан здесь короче и не растёт, почему – разобрано выше, в разговоре про мобильных операторов. Если на сервере несколько сайтов и у каждого свой журнал, в logpath можно поставить шаблон, например /var/log/nginx/*access.log. На Apache журналы лежат в /var/log/apache2/ (Debian, Ubuntu) или /var/log/httpd/ (AlmaLinux), формат строки тот же. Панели управления обычно складывают журналы по доменам в свои папки, путь смотри в настройках сайта в панели.
Логичный вопрос: зачем всё это, если есть плагины вроде Limit Login Attempts? Разница в том, где останавливается бот. Плагин блокирует его внутри WordPress, то есть каждая попытка всё равно запускает PHP, грузит движок и лезет в базу. Fail2ban отсекает адрес на уровне файрвола, и до PHP запрос уже не доходит. При ленивом переборе разницы не заметишь. Но когда в wp-login.php бьют с сотни адресов одновременно, на VPS с 1–2 ГБ памяти это разница между сайтом, который просто работает, и сайтом, который отвечает через раз.
С xmlrpc.php фильтр по коду ответа не сработает: этот адрес отвечает 200 и на удачные, и на неудачные запросы. Если ты не пользуешься мобильным приложением WordPress и Jetpack, проще закрыть его в nginx целиком:
location = /xmlrpc.php {
deny all;
} Остальные меры на уровне сервера, от прав на файлы до закрытия админки по IP, соберём в статье про защиту WordPress на сервере.
Если сайт стоит за Cloudflare
Здесь есть ловушка, которая может положить сайт целиком. Когда перед сайтом стоит Cloudflare или другой CDN, все соединения к серверу приходят с адресов этого CDN. Без дополнительной настройки nginx пишет в журнал именно их. Fail2ban видит, что «один адрес» перебирает пароли, и банит его, а через этот адрес к тебе ходят все посетители из целого региона. Сайт пропадает для всех, кого Cloudflare обслуживает через этот узел.
Лечится в два шага. Сначала nginx учат видеть настоящий адрес посетителя: модуль realip и директивы set_real_ip_from с диапазонами Cloudflare плюс real_ip_header CF-Connecting-IP. Тот же принцип, что и у nginx в роли обратного прокси. Но и тогда бан в файрволе сервера бесполезен, ведь бот стучится не напрямую, а через Cloudflare. Банить нужно на стороне Cloudflare, и для этого в Fail2ban есть готовое действие cloudflare-token, которое через API добавляет адрес в правила твоей зоны. Для этого понадобится API-токен с правами на эти правила.
От настоящего DDoS Fail2ban, к слову, не спасает ни с Cloudflare, ни без: он реагирует на строки в логе, а при атаке на канал сервер захлебнётся раньше, чем что-то успеет туда записать. Про то, чем это лечится, отдельная статья про DDoS-атаки.
Сайт в Docker: баны, которые не срабатывают
Картина знакомая: адрес бота висит в Banned IP list, а в логе nginx-контейнера тот же адрес продолжает долбить форму входа. Fail2ban при этом не сломан. Трафик к портам, которые Docker публикует наружу, не проходит через те правила файрвола, куда Fail2ban ставит баны. Docker перенаправляет его к контейнеру раньше, по своей цепочке правил. Бан добросовестно стоит, но пакеты идут мимо него.
Для контейнеров Docker оставляет специальную цепочку DOCKER-USER: правила в ней срабатывают до его собственных. Туда баны и нужно направить. В секции защиты сайта, который работает в контейнере, добавь две строки:
banaction = iptables-multiport chain = DOCKER-USER
Действие здесь именно iptables, а не nftables, потому что Docker на большинстве серверов управляет своими правилами через iptables, и бан должен попасть в ту же систему. Неочевидная деталь с портом: в этой цепочке пакет уже перенаправлен в контейнер, так что указывать нужно порт внутри контейнера. Если сайт опубликован как -p 8080:80, в port пишется 80. Ну и журнал nginx должен быть доступен Fail2ban на самом сервере, поэтому папку с логами контейнера монтируют наружу и указывают этот путь в logpath. Подробнее о том, как Docker обходится с сетью и файрволом, – в статье про Docker на VPS.
Где стоят баны и как ими управлять
Fail2ban не блокирует адреса сам, он передаёт это файрволу через действие banaction. В Ubuntu 24.04 и новее и в Debian 13 по умолчанию стоит nftables, в Debian 12 – iptables, в AlmaLinux с EPEL – firewalld. Менять это без причины не нужно, пакет подбирает вариант под систему.
С UFW связан частый вопрос: «Fail2ban вроде работает, а в ufw status никаких банов нет». Так и задумано. Fail2ban с nftables создаёт собственную таблицу правил и живёт рядом с UFW, не мешая ему, поэтому в списке UFW его баны не видны. Таблица создаётся при первом бане, и посмотреть её можно так:
sudo nft list table inet f2b-table
Если хочется, чтобы баны отображались прямо в UFW, в [DEFAULT] ставят banaction = ufw. Только учти, что при активной атаке список UFW быстро разрастается на сотни строк, и искать в нём свои правила станет неудобно. Как устроены сами файрволы, разобрано в статье про UFW и iptables.
Команды, которые пригодятся чаще остальных:
sudo fail2ban-client banned sudo fail2ban-client unban 203.0.113.45 sudo fail2ban-client set sshd banip 198.51.100.7 sudo tail -f /var/log/fail2ban.log
Первая показывает все забаненные адреса по всем защитам, вторая снимает бан с адреса везде сразу, третья банит адрес вручную (удобно, чтобы проверить, что бан реально закрывает доступ). Последняя показывает журнал самого Fail2ban в реальном времени: кого нашёл, кого забанил, какие ошибки.
Кстати, в стандартном jail.conf есть готовое действие abuseipdb, которое отправляет каждый бан в общую базу вредных адресов AbuseIPDB (нужен бесплатный ключ API). Твоему серверу это ничего не даёт, это вклад в общую копилку. Зато саму базу полезно знать: когда однажды будешь разбираться, откуда на тебя шли, по адресу там сразу видно, где ещё он успел отметиться. Анализ логов после инцидента – тема для отдельной статьи про ротацию и анализ логов.
И пара слов про другие службы. В jail.conf лежат готовые защиты для почты (postfix-sasl, dovecot), FTP, панелей и десятков других программ. Включаются они так же: секция с именем защиты, enabled = true, при необходимости backend = systemd и проверка через fail2ban-client status. Если на сервере своя почта, защиту для неё стоит включить сразу: подбор паролей к почтовым ящикам идёт так же упорно, как к SSH.
Как проверить защиту и не забанить себя
Самый честный тест – попробовать войти с неправильным паролем и посмотреть, забанит ли. Только делать это надо с другого адреса, а не с того, с которого ты сейчас сидишь в консоли. Проще всего с телефона через мобильный интернет, отключив Wi-Fi: пять раз неверный пароль, и следующее подключение должно сразу получить отказ вроде Connection refused, а в fail2ban-client status sshd появится адрес оператора.
Ситуация, которую провайдеры видят постоянно: человек тестирует Fail2ban из дома, банит собственный адрес и пишет в поддержку, что сервер упал. Сервер при этом работает, просто для одного адреса его больше нет. Параметр ignoreself, включённый по умолчанию, тут не спасает: он защищает от бана только адреса самого сервера, а не твой домашний. Внести домашний адрес в ignoreip можно, но только если он статический. У большинства домашних провайдеров адрес меняется, и через месяц в белом списке окажется адрес случайного соседа по району.
Если всё-таки забанил себя, помогает веб-консоль в панели провайдера (её называют VNC или Console). Она работает мимо сети и мимо файрвола, как монитор, подключённый к серверу напрямую. Заходишь через неё и снимаешь бан командой sudo fail2ban-client unban со своим адресом. Если консоль тоже недоступна, остаётся режим восстановления, но до него доходит редко.
Последнее, что стоит сделать после настройки, – вернуться к серверу через пару дней и посмотреть sudo fail2ban-client banned. Список обычно получается длиннее, чем ожидаешь. Если среди забаненных вдруг есть адреса, которые не похожи на ботов, или в логах видны успешные входы, которых ты не делал, это уже повод не настраивать Fail2ban дальше, а проверить признаки взлома. Остальные базовые меры, без которых Fail2ban не имеет особого смысла, собраны в статье про безопасность VPS.
Источники
- Fail2ban – репозиторий проекта, GitHub
- jail.conf – настройки по умолчанию и описание параметров, Fail2ban
- jail.conf(5) – справочная страница Fail2ban, Debian
- OpenSSH 9.8 release notes – PerSourcePenalties
- Packet filtering and firewalls – Docker Docs
- Module ngx_http_realip_module – nginx documentation
- Restoring original visitor IPs – Cloudflare Docs

