Fail2ban ставлять першим, і на цьому зазвичай усе й закінчується: пакет усередині, служба працює, sshd начебто прикритий. Через півроку з’ясовується, що джейл читає файл, якого в цій системі немає, а все інше — пошта, панель керування, форма входу на сайті — не було прикрите взагалі.

Розберімо, що варто вмикати на звичайному VPS із сайтом, які числа туди ставити і як переконатися, що джейл працює, а не просто присутній у конфігурації.

Спершу перевірте, чи ловить щось sshd

Одна команда:

sudo fail2ban-client status sshd

У виводі є рядки Currently failed, Total failed і Total banned. Якщо Total failed дорівнює нулю на сервері, який був в інтернеті хоча б добу, джейл не працює. Звірте це з реальністю:

sudo lastb | wc -l

Тисячі невдалих спроб у lastb проти нулів у Fail2ban означають рівно одне: фільтр дивиться не туди.

Найчастіша причина — Debian 12. Він більше не ставить rsyslog типово, файла /var/log/auth.log просто не існує, а типовий джейл sshd налаштований читати саме цей файл. Повідомлення про помилку при цьому немає: служба стартує, статус виводиться, лічильники лишаються на нулі. Виправлення — перехід на журнал systemd:

[sshd]
enabled = true
backend = systemd

Інший варіант — повернути rsyslog пакетом, якщо звичайний текстовий auth.log потрібен іншим інструментам. Ubuntu 24.04 поводиться так само.

Куди класти свої налаштування

Залиште /etc/fail2ban/jail.conf у спокої: він перезаписується під час оновлення пакета, і будь-яка зроблена там правка одного дня тихо зникне. Ваші налаштування йдуть у /etc/fail2ban/jail.local — цей файл читається останнім і перекриває спільний.

[DEFAULT]
ignoreip = 127.0.0.1/8 ::1 203.0.113.25
bantime  = 1h
findtime = 10m
maxretry = 5
backend  = systemd

ignoreip із власною адресою — не розкіш, а страховка: замкнути себе зовні через одну одруківку простіше, ніж здається. Якщо статичної адреси немає, тримайте другий спосіб входу, окрім SSH, — консоль провайдера або VNC.

Джейл, який змінює картину: recidive

У звичайного джейла коротка пам’ять: п’ять спроб за десять хвилин, година бану, а через годину все починається спочатку. Бот із цим цілком уживається й повернеться завтра й післязавтра. recidive закриває саме цю щілину: він читає не системний журнал, а власний журнал Fail2ban, тобто банить тих, кого Fail2ban уже банив.

[recidive]
enabled  = true
bantime  = 1w
findtime = 1d
maxretry = 5

Читайте це як «забанений п’ять разів за добу — зникає на тиждень». Джейлу потрібен файл /var/log/fail2ban.log, тож якщо ви перевели власне логування Fail2ban у syslog, recidive теж потребуватиме backend = systemd.

На практиці ефект від цього одного джейла зазвичай помітніший за тонке налаштування всіх інших: завсідники відпадають, і в журналах лишається тільки свіжий фон.

Що ще вмикати, коли на сервері є сайт

За спаданням корисності:

  • nginx-http-auth або apache-auth — перебір проти базової автентифікації. Потрібен, якщо адмінка чи тестовий сайт закриті паролем вебсервера;
  • nginx-botsearch — сканування відомих шляхів: /wp-login.php, /phpmyadmin, /.env. Це не злам, а розвідка, і саме вона передує всьому іншому;
  • nginx-limit-req — працює лише якщо в самому Nginx визначено limit_req_zone; без цього джейл увімкнений і марний;
  • postfix-sasl і dovecot — обов’язкові, якщо у вас власна пошта. Перебір паролів до скриньок іде безперервно, і зазвичай за ним ніхто не стежить.
[nginx-botsearch]
enabled = true
logpath = /var/log/nginx/access.log

Форма входу самого сайту — окрема історія. У застосунку немає власного журналу невдалих входів, а в access.log невдала спроба має вигляд звичайного POST із 200 або 302 — жодним загальним фільтром її не відрізнити від успішної. Варіантів два: змусити застосунок писати невдачі в syslog (у більшості систем керування вмістом для цього є плагін) або обмежити частоту запитів до сторінки входу. Друге — обмежувач, а не детектор, і про це краще знати заздалегідь.

Числа

bantime = 10m — типове значення, через яке Fail2ban списують як марний: десяти хвилин боту вистачає, щоб повернутися. Година на перший бан плюс recidive на тиждень працює значно краще за постійні бани, які з часом перетворюються на нескінченний список правил.

maxretry = 3 для SSH — надійний спосіб замкнути себе зовні. П’ять спроб за десять хвилин відсікають перебір анітрохи не гірше.

Повільний перебір — одна спроба на п’ять хвилин — ніколи не потрапить у findtime. Це не привід розтягувати вікно на добу: ви отримаєте хибні спрацювання проти власних колег. Проти повільного перебору працює не поріг, а вимкнена автентифікація за паролем.

Переконайтеся, що бан доходить до пакетів

Fail2ban лише викликає зовнішню команду. Якщо banaction не збігається з тим, що насправді фільтрує трафік, журнал наповнюватиметься бадьорими рядками Ban 198.51.100.7, а пакети надходитимуть далі. Debian 12 типово використовує nftables, а з увімкненим UFW правильний вибір — banaction = ufw. Для перевірки:

sudo nft list ruleset | grep -c f2b
sudo iptables -S | grep f2b

Хоча б одна з двох має показати правила. А сам фільтр можна перевірити, не чекаючи атаки:

sudo fail2ban-regex systemd-journal /etc/fail2ban/filter.d/sshd.conf

Унизу виводу написано, скільки рядків збіглося. Нуль означає, що фільтр і журнал не зустрічаються, і налаштовувати далі немає сенсу.

Чого Fail2ban не робить

Він не захищає від розподіленого перебору: тисяча адрес з однією спробою кожна не досягне порога за жодних налаштувань. Він не усуває причину — забанена адреса не скасовує слабкий пароль. І він нічого не знає про репутацію: адреса, яка вчора ламала чужі сервери, для нього чиста, доки не візьметься за ваш. Звідси комбінація, яку застосовують на практиці: ключі замість паролів, Fail2ban як обмежувач шуму і спільний список блокувань (CrowdSec) як знання чужого досвіду.

Окрема проблема — усе це треба час від часу переглядати. Ніхто тижнями не набирає fail2ban-client status по кожному джейлу, а зростання кількості банів помічають, коли щось уже зламалося. На демосторінках нижче ті самі дані лежать на одній сторінці: список джейлів, хто забанений просто зараз і звідки йдуть спроби.