Когда я впервые столкнулся с массированной атакой на свой SSH-сервер, в логах мелькали десятки тысяч попыток подбора паролей за час. Стандартные настройки безопасности оказались бесполезными против распределённого ботнета. Именно тогда я понял: Fail2Ban нужно настраивать не поверхностно, а глубоко, создавая собственные фильтры под каждый тип атаки. И результат превзошёл ожидания.
Архитектура Fail2Ban: три кита защиты
Fail2Ban работает по принципу непрерывного мониторинга логов через три взаимосвязанных компонента. Первый уровень составляют фильтры в директории /etc/fail2ban/filter.d/, содержащие регулярные выражения failregex для обнаружения подозрительных паттернов. Второй уровень это jail-ы, которые связывают фильтр с конкретным лог-файлом и определяют параметры срабатывания: maxretry (число попыток), findtime (временное окно) и bantime (длительность блокировки). Третий уровень представляют actions в /etc/fail2ban/action.d/, исполняющие команды блокировки через iptables, nftables, firewalld или кастомные скрипты.
Критически важный момент: никогда не редактируйте дефолтные файлы jail.conf или filter.conf напрямую. При обновлении пакета все изменения будут потеряны. Вместо этого создавайте файлы с расширением .local или помещайте конфигурации в каталоги jail.d/ и filter.d/. Система Fail2Ban автоматически применит ваши настройки поверх дефолтных, сохранив их при любых обновлениях.
Основной механизм работы выглядит так: Fail2Ban использует backend для чтения логов (polling, systemd, pyinotify), применяет regex-фильтры к каждой строке, подсчитывает совпадения для каждого IP-адреса в окне findtime и при превышении maxretry запускает actionban. После истечения bantime выполняется actionunban и IP освобождается. Вся информация о банах записывается в /var/log/fail2ban.log, который позже используется фильтром recidive.
SSH: создание агрессивных фильтров
Стандартный фильтр sshd охватывает базовые случаи неудачной аутентификации, но современные атаки требуют более изощрённого подхода. Фильтр sshd поддерживает режимы через параметр mode: normal, ddos, extra и aggressive. Чтобы активировать агрессивный режим, в секции jail укажите:
[sshd]
enabled = true
filter = sshd[mode=aggressive]
logpath = /var/log/auth.log
maxretry = 3
bantime = 1h
findtime = 10m
Агрессивный режим включает дополнительные паттерны, такие как блокировка по быстрым отключениям на этапе preauth. Эти отключения часто сигнализируют о сканировании портов или автоматизированных проверках уязвимостей. Строка в логе выглядит примерно так: "Connection closed by authenticating user root 192.168.1.100 port 52314 [preauth]". Дефолтный режим пропускает такие события, но они критически важны для раннего обнаружения.
Для создания собственного расширенного фильтра создайте файл /etc/fail2ban/filter.d/sshd-custom.local:
[Definition]
failregex = ^%(__prefix_line)sFailed password for .* from <HOST>.*$
^%(__prefix_line)sFailed \S+ for (?P<cond_inv>invalid user )?(?P<user>\S+) from <HOST>
^%(__prefix_line)sInvalid user .* from <HOST>
^%(__prefix_line)sConnection closed by authenticating user .* <HOST> port \d+ \[preauth\]$
^%(__prefix_line)sReceived disconnect from <HOST>: 11: Bye Bye
^%(__prefix_line)sUser .+ from <HOST> not allowed because not listed in AllowUsers
ignoreregex =
Здесь используется тег <HOST>, который Fail2Ban автоматически заменяет на regex для IPv4 и IPv6. Переменная __prefix_line подставляет стандартный префикс лог-строки с датой и временем. Обратите внимание на именованные группы типа (?P<user>\S+), которые позволяют извлекать дополнительную информацию для логирования.
Критически важно понимать флаги в regex-файлах. В документации Fail2Ban упоминаются флаги типа F-MLFID (multi-line filter ID), F-NOFAIL (не считать как failure) и другие, которые управляют внутренней логикой обработки. Флаг <F-MLFID> позволяет создавать многострочные паттерны для сложных атак, когда признаки взлома распределены по нескольким строкам лога.
Для тестирования созданного фильтра используйте команду:
fail2ban-regex /var/log/auth.log /etc/fail2ban/filter.d/sshd-custom.local --print-all-matched
Эта команда покажет все совпавшие строки и извлечённые IP-адреса. Если видите ноль совпадений при наличии атак в логе, значит regex требует корректировки.
Защита веб-приложений: WordPress и не только
Веб-приложения атакуются совершенно иначе. Здесь нет чётких сообщений "Failed password", вместо этого нужно анализировать HTTP-коды ответов, паттерны URL-запросов и поведенческие аномалии. Для WordPress основные векторы атак это брутфорс wp-login.php и эксплуатация xmlrpc.php для DDoS-усиления.
Создайте фильтр /etc/fail2ban/filter.d/wordpress-auth.conf:
[Definition]
failregex = ^<HOST> -.*"(POST|GET).*(wp-login\.php).*" (200|401|403)
^<HOST> -.*"(POST|GET).*(xmlrpc\.php).*" (200|401|403)
ignoreregex =
Обратите внимание: здесь мы ловим код 200, потому что WordPress возвращает успешный HTTP-ответ даже при неудачном логине. Страница просто перезагружается с ошибкой. Если вы используете плагины безопасности типа Wordfence или Limit Login Attempts, они могут логировать неудачные попытки с кодом 403, что упрощает детектирование.
Для более агрессивной защиты создайте фильтр от сканеров уязвимостей. Эти боты постоянно ищут .env файлы, директории .git, phpMyAdmin и другие типичные точки входа:
[Definition]
failregex = ^<HOST> -.*"(GET|POST|HEAD).*(/\.env|/\.git|/phpmyadmin|/adminer|/wp-config\.php\.bak).*"
^<HOST> -.*"(GET|POST).*(\.\./\.\./|\%2e\%2e/|union.*select|script>).*"
maxretry = 1
Здесь maxretry установлен в 1, потому что легитимный пользователь никогда не обращается к этим путям. Единственный запрос к .env это уже признак сканирования.
Продвинутая техника: защита от HTTP POST-флуда через анализ логов ошибок Nginx. Если вы настроили limit_req_zone в Nginx, превышения лимита записываются в error.log:
[Definition]
failregex = limiting requests, excess:.* by zone ".*", client: <HOST>
Jail для этого фильтра может выглядеть так:
[nginx-limit-req]
enabled = true
filter = nginx-limit-req
logpath = /var/log/nginx/error.log
maxretry = 10
findtime = 30
bantime = 6h
Это позволяет банить агрессивных ботов на длительный срок, в то время как сам Nginx даёт им временную задержку на несколько секунд.
Ban Actions: от iptables до интеграции с облаками
Стандартное действие Fail2Ban это создание правил firewall через iptables-multiport или iptables-allports. При запуске службы Fail2Ban создаёт отдельную цепочку правил для каждого активного jail, например fail2ban-sshd. Это элегантное решение не засоряет основные правила firewall и легко откатывается при остановке службы.
Действие iptables-multiport блокирует только указанные порты:
iptables -I fail2ban-sshd 1 -s 192.168.1.100 -j REJECT
Действие iptables-allports изолирует IP полностью:
iptables -I fail2ban-sshd 1 -s 192.168.1.100 -j DROP
Разница между REJECT и DROP в том, что REJECT отправляет ICMP-пакет обратно, информируя об отказе, а DROP молча игнорирует пакеты. Для атакующих DROP предпочтительнее, так как не даёт информации о работающем firewall.
Для веб-серверов за Cloudflare стандартные iptables-действия бесполезны, так как вы видите только IP Cloudflare, а не реального атакующего. Решение: использовать API Cloudflare для блокировки. Создайте файл /etc/fail2ban/action.d/cloudflare.conf:
[Definition]
actionban = curl -s -X POST "https://api.cloudflare.com/client/v4/zones/<ZONE_ID>/firewall/access_rules/rules" \
-H "X-Auth-Email: <CLOUDFLARE_EMAIL>" \
-H "X-Auth-Key: <API_KEY>" \
-H "Content-Type: application/json" \
--data '{"mode":"block","configuration":{"target":"ip","value":"<ip>"},"notes":"Banned by Fail2Ban"}'
actionunban = curl -s -X DELETE "https://api.cloudflare.com/client/v4/zones/<ZONE_ID>/firewall/access_rules/rules/<ID>" \
-H "X-Auth-Email: <CLOUDFLARE_EMAIL>" \
-H "X-Auth-Key: <API_KEY>"
Теги <ip>, <name>, <matches> автоматически заменяются Fail2Ban при исполнении. Это обеспечивает универсальность действий.
Другой полезный вариант: интеграция с AbuseIPDB для репортинга атакующих IP. Это помогает другим администраторам и улучшает глобальную базу угроз. В action добавьте:
actionban = curl -s "https://api.abuseipdb.com/api/v2/report" \
-H "Key: <API_KEY>" -H "Accept: application/json" \
--data-urlencode "ip=<ip>" --data-urlencode "categories=18,22"
Recidive: многоуровневая защита от настойчивых
Механизм recidive это метаджейл, отслеживающий IP, которые уже были забанены другими jail-ами. Технически он мониторит собственный лог Fail2Ban в /var/log/fail2ban.log и ищет строки вида "[jail-name] Ban <HOST>". Стандартный фильтр recidive.conf содержит regex:
[Definition]
failregex = ^%(__prefix_line)s(?:\s*fail2ban\.actions\s*%(__pid_re)s?:\s+)?NOTICE\s+\[<_jailname>\]\s+Ban\s+<HOST>
Критически важно исключить сам recidive из отслеживания, иначе возникнет петля. В regex используется тег <_jailname>, который должен быть настроен на игнорирование самого себя.
Типичная конфигурация recidive:
[recidive]
enabled = true
filter = recidive
logpath = /var/log/fail2ban.log
banaction = iptables-allports
bantime = 1w
findtime = 1d
maxretry = 5
Это означает: если IP был забанен 5 раз за последние сутки в любых jail-ах, заблокировать его на неделю по всем портам. Для супер-агрессивных атакующих можно создать второй уровень recidive-long с параметрами bantime = 4w, findtime = 1w, maxretry = 3.
Продвинутая техника: перманентные баны через ipset и blocklist. Создайте action, который при срабатывании recidive добавляет IP не только в iptables, но и в постоянный файл:
echo "<ip>" >> /etc/fail2ban/ip.blocklist.recidive
ipset add fail2ban-permanent <ip>
При старте системы загружайте этот список:
while read ip; do ipset add fail2ban-permanent "$ip"; done < /etc/fail2ban/ip.blocklist.recidive
Важный нюанс: если уровень логирования установлен в DEBUG, сам процесс бана recidive создаёт записи в лог, что может привести к петле. Установите loglevel = INFO или WARNING в fail2ban.local.
Современные версии Fail2Ban (0.11+) поддерживают bantime.increment для автоматического увеличения времени бана без recidive:
[DEFAULT]
bantime.increment = true
bantime.factor = 1
bantime.formula = ban.Time * (1<<(ban.Count if ban.Count<20 else 20)) * banFactor
Эта формула удваивает время бана при каждом повторном нарушении, ограничивая максимум 20 итерациями. Первый бан 1 час, второй 2 часа, третий 4 часа и так далее.
Тестирование regex: проверяй дважды
Самая распространённая ошибка создание regex без тестирования на реальных логах. Команда fail2ban-regex позволяет прогнать фильтр по логу и увидеть все совпадения:
fail2ban-regex /var/log/nginx/access.log /etc/fail2ban/filter.d/wordpress-auth.conf
Добавьте флаг --print-all-missed для отображения строк, которые regex пропустил. Это критически важно для отладки.
Опасность плохих regex: если удалённый пользователь может внедрить текст, который совпадёт с failregex и захватит часть <HOST>, он получит возможность заблокировать любой IP по своему выбору. Поэтому всегда закрепляйте regex на текст, генерируемый приложением, а не пользователем. Используйте ^ в начале и $ в конце для полного соответствия строке.
Предпочитайте неалчные совпадения .*? вместо .*, особенно перед тегом <HOST>. Алчное совпадение захватывает максимум символов, что может привести к неправильному извлечению IP, если в строке несколько адресов.
После создания конфигураций проверьте их работу:
fail2ban-client reload
fail2ban-client status
fail2ban-client status sshd
Симулируйте атаку с тестового хоста и проверьте, появился ли IP в списке забаненных. Используйте fail2ban-client set sshd banip 192.168.1.100 для ручного бана при тестировании actions.
Fail2Ban превращается из простого инструмента в комплексную оборону только при глубокой настройке под специфику вашего проекта. Универсальных конфигураций не существует, каждый сервер требует анализа атак и адаптации фильтров. Но результат окупается тишиной в логах и спокойствием, что ваша инфраструктура защищена не просто паролями, а интеллектуальной системой, которая учится и адаптируется к новым угрозам.