- Опубликовано: 7 июл 2026
- 27
Fail2ban и брутфорс: как перестать кормить ботов
Часть 3 из 14 · Серия «Сервер в бою»
В прошлой статье я обещал показать самый тревожный лог сервера. Выполняю. Если у вас есть VPS с публичным IP, откройте прямо сейчас:
grep "Failed password" /var/log/auth.log | wc -l
У свежего сервера, о котором ещё никто не должен знать, это число за сутки спокойно доходит до нескольких тысяч. Никто вас лично не взламывает — это фоновая радиация интернета: ботнеты круглосуточно сканируют все IPv4-адреса подряд и стучатся в каждый открытый SSH со словарём из root/123456, admin/admin и ещё пары миллионов комбинаций. Ваш сервер попал в этот список в первые же минуты после создания.
Опасно ли это? Если у вас нормальный пароль или вход по ключу — почти нет. Но «почти нет» — плохая политика безопасности, а кроме риска есть ещё и цена: каждая попытка входа — это работа sshd, запись в лог, крошка CPU. Тысячи попыток в день — и вы буквально кормите ботов ресурсами своего VPS. В этой статье мы поставим на дверь вышибалу — fail2ban, — настроим его для SSH и WordPress, а главное, обойдём грабли, на которые наступает каждый первый (включая граблю «забанил сам себя»).
Как это выглядит в auth.log: «до»
Сначала — посмотреть врагу в лицо, мы же договорились начинать с логов. Открываем:
tail -f /var/log/auth.log
И просто ждём. Долго ждать не придётся:
Jul 4 03:41:02 vps sshd[19204]: Failed password for root from 45.155.204.11 port 41522 ssh2
Jul 4 03:41:05 vps sshd[19204]: Failed password for root from 45.155.204.11 port 41522 ssh2
Jul 4 03:41:09 vps sshd[19208]: Invalid user admin from 45.155.204.11 port 41916
Jul 4 03:41:11 vps sshd[19208]: Failed password for invalid user admin from 45.155.204.11 port 41916
Jul 4 03:41:15 vps sshd[19213]: Invalid user oracle from 193.32.162.85 port 55210
Методичный перебор: root, admin, oracle, postgres, user, test... Знакомый по прошлой статье однострочник покажет топ самых настойчивых:
grep "Failed password" /var/log/auth.log | awk '{print $(NF-3)}' | sort | uniq -c | sort -rn | head
1847 45.155.204.11
923 193.32.162.85
411 118.25.6.39
Почти две тысячи попыток с одного адреса. Вручную банить такое бессмысленно — адресов сотни, и они меняются. Нужна автоматика.
Сначала — дверь, потом — вышибала
Честная оговорка перед установкой чего бы то ни было: fail2ban — не замена нормальной аутентификации, а дополнение к ней. Если вход на сервер возможен по паролю qwerty123, никакой бан после пятой попытки не спасёт — ботнету хватит и четырёх, просто с разных IP.
Поэтому базовая гигиена, десять минут, один раз:
1. Вход только по ключу. На своей машине: ssh-keygen -t ed25519, затем ssh-copy-id user@сервер. Проверили, что входит без пароля, — и только после этого:
2. Отключаем парольный вход и root. В /etc/ssh/sshd_config:
PasswordAuthentication no
PermitRootLogin prohibit-password
И systemctl restart ssh. Важно: не закрывайте текущую SSH-сессию, пока не проверили в соседнем окне, что новая открывается. Это правило номер ноль любых манипуляций с SSH — старая сессия остаётся вашим запасным выходом.
После этих двух шагов брутфорс становится математически бесполезным: подобрать ключ ed25519 перебором нельзя. Но боты об этом не знают и продолжат долбиться, засоряя логи и отъедая ресурсы. Вот теперь — вышибала.
Ставим fail2ban и понимаем, как он думает
apt install fail2ban
Идея fail2ban гениально проста, он делает ровно то, что мы делали в прошлой статье руками, только непрерывно: читает лог → ищет строки-признаки атаки по регуляркам (фильтр) → считает совпадения от одного IP → при превышении порога банит IP через файрвол на заданное время. Связка «какой лог + какой фильтр + какой порог + какое наказание» называется jail — камера.
Первая грабля ждёт прямо здесь. Конфиг по умолчанию лежит в /etc/fail2ban/jail.conf, и править его нельзя: при обновлении пакета файл перезапишется, и ваши настройки испарятся. Все свои изменения — только в отдельном файле:
nano /etc/fail2ban/jail.local
Разумный стартовый вариант:
[DEFAULT]
# кого не банить никогда (localhost и ВАШ домашний/офисный IP)
ignoreip = 127.0.0.1/8 ::1 203.0.113.50
# порог: сколько промахов за какое окно
maxretry = 5
findtime = 10m
# на сколько баним
bantime = 1h
# рецидивистам — больнее: каждый повторный бан длиннее
bantime.increment = true
bantime.maxtime = 1w
[sshd]
enabled = true
Перезапускаем и смотрим статус:
systemctl restart fail2ban
fail2ban-client status sshd
Status for the jail: sshd
|- Filter
| |- Currently failed: 3
| `- Total failed: 214
`- Actions
|- Currently banned: 7
`- Total banned: 19
Семь адресов уже сидят. Fail2ban начал отрабатывать в первые же минуты — не потому что он такой быстрый, а потому что стучатся действительно непрерывно.
Про ignoreip — отдельно и с нажимом: впишите туда свой IP до того, как понадобится. Классика жанра: админ настраивает fail2ban, потом возится с SSH-ключами, пять раз промахивается паролем — и наблюдает Connection refused от собственного сервера. Если IP динамический и меняется — хотя бы держите под рукой аварийную VNC-консоль провайдера (мы говорили о ней в первой статье): разбан оттуда делается одной командой:
fail2ban-client set sshd unbanip 203.0.113.50
Auth.log «после»: тишина в цифрах
Через сутки после включения сравните сами:
grep "Failed password" /var/log/auth.log | wc -l
Число падает на порядок — с тысяч до сотен и меньше. Не до нуля: каждому новому боту всё ещё дают его пять попыток, прежде чем захлопнуть дверь, — но методичный перебор словаря с одного адреса умер как жанр. А с bantime.increment постоянные «клиенты» постепенно уезжают в бан на неделю, и фон продолжает снижаться со временем.
Это, кстати, приятная особенность fail2ban: эффект видно в тех же логах, по которым мы диагностировали проблему. Метод «сначала смотрим» работает в обе стороны — и для поиска болезни, и для проверки лечения.
Уровень два: WordPress и другие двери
SSH — не единственная дверь, в которую ломятся. Если на сервере крутится WordPress, вторая по популярности мишень — форма логина wp-login.php и xmlrpc.php. Брутфорс здесь виден не в auth.log, а в access.log nginx — сплошные POST-запросы:
grep "POST /wp-login.php" /var/log/nginx/access.log | awk '{print $1}' | sort | uniq -c | sort -rn | head
Fail2ban легко научить читать и этот лог. Создаём фильтр /etc/fail2ban/filter.d/wordpress-login.conf:
[Definition]
failregex = ^<HOST> .* "POST /(wp-login\.php|xmlrpc\.php)
И jail в наш jail.local:
[wordpress-login]
enabled = true
port = http,https
filter = wordpress-login
logpath = /var/log/nginx/access.log
maxretry = 5
findtime = 5m
bantime = 6h
Тонкость, о которую спотыкаются: этот фильтр считает все POST на логин, включая успешный вход настоящего админа. Для персонального блога, куда логинитесь вы один, maxretry = 5 — нормально: вы просто не промахнётесь пять раз за пять минут (а если промахнётесь — вы в ignoreip, правда ведь?). Для сайта с множеством живых пользователей порог стоит поднять, а по-хорошему — банить по коду ответа или логу неудачных входов самого WordPress. Начните с простого варианта, посмотрите неделю на fail2ban-client status wordpress-login — и подкрутите под свой трафик.
По тому же шаблону — «найди признак атаки в логе, напиши регулярку, заведи jail» — защищается что угодно: панель phpMyAdmin, форма логина вашего Laravel-приложения, почтовый сервер. У fail2ban из коробки уже есть десятки готовых фильтров в /etc/fail2ban/filter.d/ — загляните, ваша ситуация там наверняка предусмотрена. Проверить свежесочинённую регулярку на живом логе, не дожидаясь атаки:
fail2ban-regex /var/log/nginx/access.log /etc/fail2ban/filter.d/wordpress-login.conf
Типичные грабли: чек-лист
Соберём в кучу всё, обо что бьются на практике.
| Грабля | Симптом | Лекарство |
|---|---|---|
Правили jail.conf |
Настройки пропали после apt upgrade |
Всё своё — в jail.local |
Забыли ignoreip |
Забанили сами себя | VNC-консоль → unbanip, потом вписать свой IP |
| SSH на нестандартном порту | Баны «не работают» | fail2ban обычно берёт порт из конфига sshd сам, но проверьте port = в jail |
| Смотрят не тот лог | Jail молчит при явной атаке | Сверьте logpath с реальностью; на новых системах sshd может писать только в journald — тогда backend = systemd |
bantime навсегда |
Список банов растёт вечно, в него попадают и живые люди с динамических IP | Часы, не годы; для рецидивистов — bantime.increment |
| Fail2ban как единственная защита | Пароль 123456 «под защитой» |
Сначала ключи и PasswordAuthentication no, потом всё остальное |
| Nginx за Cloudflare/прокси | Банятся IP прокси, т.е. весь сайт | Настроить real_ip_header, чтобы в логи попадал реальный IP клиента |
Последний пункт — самый коварный, потому что стреляет не сразу: всё выглядит рабочим, пока однажды fail2ban не забанит IP-адрес Cloudflare и сайт не «упадёт» для всех сразу. Если между пользователем и nginx есть прокси — сначала научите nginx писать в лог настоящие адреса, и только потом натравливайте на этот лог баны.
Что в итоге
Полчаса работы — и картина меняется радикально: вход на сервер возможен только по ключу, словарный брутфорс SSH математически мёртв, боты после пятой попытки уезжают в бан с прогрессирующим сроком, WordPress-логин прикрыт, а auth.log из сплошного потока Failed password превратился в читаемый журнал, где настоящее вторжение (если оно когда-нибудь случится) будет видно, а не утонет в шуме. Именно это, а не «неуязвимость», и есть цель: снизить фон, чтобы слышать сигнал.
Кстати, о «слышать». Fail2ban защищает молча — а хотелось бы ещё и знать, что происходит: когда сервер под нагрузкой, когда кончается диск, когда сайт лёг. Пока что все наши инструменты реагируют постфактум: авария случилась — пошли читать логи. В следующей статье перевернём подход и научим сервер сообщать о проблемах самому — поставим Uptime Kuma и Netdata и разберёмся, как мониторить одинокий VPS, не превращая это в вторую работу.
- 1 Сервер упал в три ночи: разбор полётов без паники
- 2 Логи, которые говорят: учимся читать journalctl, nginx и syslog
- 3 Fail2ban и брутфорс: как перестать кормить ботов вы здесь
- 4 Мониторинг для одного сервера: Uptime Kuma, Netdata и здравый смысл скоро
- 5 Диск закончился внезапно: куда деваются гигабайты на VPS скоро
- 6 Обновления без страха: apt upgrade, который ничего не сломает скоро
- 7 Свой VPN на VPS за полчаса: WireGuard без магии скоро
- 8 Несколько сайтов на одном VPS: делим ресурсы без драки скоро
- 9 Тормозит сайт — виноват не хостинг: тюним nginx и PHP-FPM скоро
- 10 MySQL на маленьком VPS: почему база съедает всю память скоро
- 11 Кэширование на пальцах: Redis, FastCGI cache и когда хватит просто заголовков скоро
- 12 Cron, systemd-таймеры и скрипты: автоматизируем рутину сервера скоро
- 13 Деплой без FTP в 2026-м: git pull, хуки и простой CI/CD на своём VPS скоро
- 14 Переезд на другой VPS без даунтайма: чек-лист миграции скоро
Была статья полезной: