Bruteforce по SSH и другим публичным сервисам редко «ломает» пароль мгновенно, но почти всегда создаёт нагрузку: забивает логи, держит демоны в busy, нагружает сеть и подсистему аутентификации. На практике важнее быстро и предсказуемо срезать поток попыток, чем пытаться угадать все IP-адреса атакующих.
На Linux для этого удобно комбинировать два слоя:
- nftables — режет шум на входе: ограничивает скорость новых подключений (rate limit) и/или число параллельных соединений (connlimit-логика).
- Fail2ban — по логам выделяет «упорных» нарушителей и банит их на минуты/часы.
Ниже — практическая схема: сначала базовые лимиты в nftables, затем привязка Fail2ban к nft set без зоопарка из разных фаерволов.
Что именно ограничивать: параллельные соединения и скорость новых попыток
У bruteforce по SSH обычно два профиля, и под каждый нужен свой тормоз:
- Шторм коротких подключений (частые new-сессии): важно ограничить скорость новых соединений на порт 22, то есть
ct state new. - Накопление параллельных сессий (держат много соединений «висящими»): важно ограничить количество одновременных соединений на источник (connlimit-логика).
Эти лимиты не взаимозаменяемы: можно иметь низкую скорость новых соединений, но много параллельных (если соединения держат долго), и наоборот.
Где применять лимиты: на входе или на уровне демона
Оптимально ограничивать на цепочке input в таблице inet — до того, как соединение доберётся до SSH и начнёт потреблять ресурсы приложения. При этом почти всегда лимитируют именно ct state new, чтобы не ломать уже установленные интерактивные сессии.
Практический принцип: rate limit режет «всплески», connlimit защищает от «залипания». Вместе они дают предсказуемое поведение под атакой.
Базовый каркас nftables для SSH: минимально безопасно и расширяемо
Ниже — каркас, куда удобно встраивать SSH-защиту. Семейство inet позволяет одним ruleset покрыть IPv4 и IPv6. Политика по умолчанию — drop, так что убедитесь, что добавили все нужные разрешения (SSH, мониторинг, сервисы).
#!/usr/sbin/nft -f
flush ruleset
table inet filter {
chain input {
type filter hook input priority 0;
policy drop;
ct state established,related accept
iifname "lo" accept
ip protocol icmp accept
ip6 nexthdr icmpv6 accept
tcp dport 22 ct state new jump ssh_guard
tcp dport 22 accept
reject with icmpx type admin-prohibited
}
chain ssh_guard {
return
}
}
Почему отдельная цепочка ssh_guard: туда удобно складывать rate limit, проверки по динамическим наборам (sets) от Fail2ban, счётчики и аккуратное логирование.
Если вы держите проекты на VDS, такой каркас помогает быстро включить базовую защиту до установки/настройки прикладных средств — и затем спокойно её расширять.

Ограничиваем скорость новых SSH-подключений (rate limit по источнику)
Самая надёжная и переносимая часть: ограничивать частоту новых соединений на порт 22. В nftables это делается через выражение limit в сочетании с ct state new. По смыслу это «hashlimit-подобная» логика: вы ограничиваете поток событий, не трогая уже установленные соединения.
Пример: разрешить до 5 новых соединений в минуту (с небольшим burst), всё сверх — отбрасывать. Значения подбирайте под реальность (офисный NAT, VPN, bastion, автоматизация).
table inet filter {
chain ssh_guard {
ct state new limit rate 5/minute burst 10 packets counter return
counter
log prefix "SSH rate limit: " flags all level warning
drop
}
}
Почему return: мы возвращаемся в input, где дальше сработает tcp dport 22 accept. Цепочка ssh_guard отвечает только за «лишнее».
Как подобрать значения, чтобы не заблокировать себя
- Только вы с одного IP: обычно хватает 3–5 новых соединений в минуту,
burst6–12. - Офис/провайдерский NAT: увеличивайте лимит или делайте allowlist для офисного IP/подсети до лимитов.
- CI/CD, Ansible, мониторинг: частые короткие SSH-сессии легко утыкаются в лимит. Для таких источников — отдельные правила (allowlist) или доступ через bastion.
Если сомневаетесь — начните только с rate limit на
ct state new. Он даёт максимальный эффект против bruteforce при минимальном риске само-блокировки.
Connlimit-логика: ограничиваем параллельные SSH-сессии
Идея: один реальный пользователь редко держит десятки одновременных SSH-сессий с одного IP, а атака может держать много «висящих» соединений, съедая дескрипторы, память и обработчики.
Нюанс: в iptables был привычный модуль connlimit, а в nftables «как в одну строку для всех» может отличаться по синтаксису и возможностям в зависимости от версии ядра и nft. Поэтому безопасный подход такой:
- rate limit по
ct state new— основная линия защиты (стабильно и почти везде одинаково работает); - ограничение параллельных соединений — как дополнительный слой, который вы внедряете после теста на своей платформе.
Практическая заготовка (как «место под connlimit»): добавьте отдельное правило в ssh_guard до rate limit и проверьте поддержку конкретного выражения в вашей версии nft. Если выражение не поддерживается, оставьте только rate limit и компенсируйте на стороне sshd параметрами вроде MaxStartups и таймаутами.
table inet filter {
chain ssh_guard {
# Connlimit-логика (пример-ориентир): если уже слишком много активных SSH-соединений от источника,
# то новые попытки режем.
# Синтаксис зависит от версии nft/ядра, поэтому внедряйте только после проверки.
# пример-ориентир (псевдокод): ct count over 6 drop
return
}
}
Если хочется углубиться именно в динамические механики (sets, таймауты, массовые блок-листы), полезно посмотреть материал про динамические наборы в nftables и автоматические блокировки.
Fail2ban + nftables: кто за что отвечает
Роли лучше разделить так:
- nftables — мгновенно и дёшево режет поток (rate limit/connlimit-логика) и отбрасывает явно лишнее.
- Fail2ban — по логам SSH определяет повторяющиеся неуспешные логины и банит источник на время.
Лучший способ «подружить» их — чтобы Fail2ban писал IP-адреса в nftables set, а nftables проверял этот set самым первым правилом для SSH. Тогда правила остаются компактными, а баны применяются мгновенно.
Создаём nft set под баны и проверяем его в начале ssh_guard
Пример для IPv4. Если у вас есть IPv6-доступ на SSH — заведите второй набор для ipv6_addr и проверяйте ip6 saddr отдельно.
table inet filter {
set f2b_ssh4 {
type ipv4_addr
flags timeout
}
chain ssh_guard {
ip saddr @f2b_ssh4 counter drop
ct state new limit rate 5/minute burst 10 packets counter return
counter drop
}
}
Почему set лучше, чем «отдельное правило на каждый IP»:
- меньше правил и быстрее обработка при большом числе банов;
- удобные таймауты на уровне nftables;
- проще сопровождать и отлаживать.
Какие команды должен выполнять action Fail2ban (концептуально)
Fail2ban action обычно делает добавление/удаление элемента в set. Важно, чтобы таблица и set существовали до старта Fail2ban, иначе бан «не прилипнет».
nft add element inet filter f2b_ssh4 { 203.0.113.10 timeout 1h }
nft delete element inet filter f2b_ssh4 { 203.0.113.10 }
Если вы одновременно настраиваете и периметр, и защиту админки, держите единый подход (один фаервол-стек). Отдельно может пригодиться разбор общей практики защиты SSH на сервере: как аккуратно закрывать SSH фаерволом без потери доступа.
Отладка: как понять, что ограничения реально работают
Смотрим правила и счётчики
Счётчики (counter) — лучший компромисс: видно, что срабатывает, но нет лог-DoS при атаке.
nft list ruleset
nft list chain inet filter ssh_guard
nft list set inet filter f2b_ssh4
Проверяем симптомы «залипания» соединений
Если атака держит много установленных соединений, смотрите реальную картину по сокетам и общему числу established:
ss -tan sport = :22 | head
ss -tan state established '( sport = :22 )' | wc -l
Если вы утыкаетесь в лимиты conntrack на уровне системы, одного SSH-лимита может быть мало: нужно разбирать общую сетевую нагрузку и параметры conntrack — это уже отдельная задача.

Типовые ошибки и как их избежать
Ошибка 1: лимитируете весь трафик на порт 22 вместо ct state new
Если ограничивать не новые подключения, а все пакеты, можно «подрезать» уже установленные SSH-сессии. Для SSH почти всегда лимитируют именно новые соединения: ct state new.
Ошибка 2: слишком жёсткий лимит без burst
Плохая сеть, автопереподключения клиента, multiplexing, параллельные задачи — всё это может кратковременно дать всплеск. Добавляйте burst и сначала тестируйте в двух сессиях, имея доступ к консоли провайдера.
Ошибка 3: агрессивное логирование на каждый drop
При атаке логирование само становится DoS. Лучше опираться на counter, а если нужен лог — логируйте реже отдельным правилом с дополнительным limit.
Ошибка 4: Fail2ban банит не туда
Частая история: nftables фильтрует трафик, а Fail2ban продолжает банить через iptables, или наоборот. Приведите всё к одной модели и явно проверьте, что баны попадают именно в ваш nft set.
Практический рецепт «за 10 минут»
Вынесите SSH в отдельную цепочку
ssh_guard.Добавьте rate limit на
ct state newс разумнымburstиcounter.Создайте
nft setпод баны Fail2ban и проверяйте его первым правилом вssh_guard.Настройте Fail2ban action на
nft add elementиnft delete elementдля этого набора.Проверьте счётчики и убедитесь, что при тестах растёт число срабатываний на лимитах.
Итоги
Связка nftables + Fail2ban хорошо работает, если разделить обязанности: nftables мгновенно «гасит шум» (rate limit новых соединений и при необходимости connlimit-логика), а Fail2ban точечно банит тех, кто систематически ломится по логам аутентификации.
Делайте отдельную цепочку под SSH, лимитируйте именно ct state new, используйте burst, полагайтесь на counter вместо лог-спама — и получите устойчивую защиту, которая не мешает администрированию даже под заметной атакой.


