Начиная с OpenSSH 9.7, sshd умеет автоматически ограничивать на время подозрительные IP без Fail2Ban и iptables. В Ubuntu 26.04 эта функция уже включена по умолчанию — даже если в sshd_config про неё ничего не написано. Предлагаю попробовать разобраться с тем, как это работает.
Disclaimer: статья написана без использования ИИ. Нейросеть использовалась только для стилистической редактуры. Ничего не рекламирую и в ТГ-чаты не зазываю. Гараж не продаю.

Как вы знаете, в прошлом месяце вышла новая LTS-версия Ubuntu 26.04 — Resolute Raccoon. Читая патчноуты (да, существует много людей, которые их читают), в пункте про OpenSSH я обнаружил вот такой интересный момент: новая директива PerSourcePenalties будет «наказывать» адреса клиентов, которые по какой-либо причине не завершают аутентификацию.
Вообще, функция далеко не новая. Разработчики OpenSSH добавили её ещё в 2024 году (рассылка) и включили по умолчанию в версии 9.7, но эта версия не попала в Ubuntu 24.04.

В Ubuntu 26.04 «из коробки» поставляется OpenSSH версии 10.2p1 от 27 января 2026. Следовательно, по умолчанию теперь всё включено. Я раскатил себе официальный образ, полез смотреть конфиги, и… в них ничего нет! Получается, что функциональность есть и включена, но в конфиге нет ни одного слова про это. И если вы не читали патчноуты, то даже не знаете что оно у вас есть.
В Ubuntu 26.04 проверить, что оно включено, можно командой:
sshd -T | grep -i persource

Нас интересует строка:
persourcepenalties crash:90 authfail:5 noauth:1 grace-exceeded:10 refuseconnection:10 max:600 min:15 max-sources4:65536 max-sources6:65536 overflow:permissive overflow6:permissive
Параметры новой директивы
Давайте разберём каждый параметр по порядку.
Основные штрафы (в секундах)
crash:90 — самый жёсткий штраф. Присваивается, если клиент вызывает сбой или аномальное завершение процесса sshd. Это защита от потенциальных эксплойтов и нестабильных клиентов.
authfail:5 — неудачная аутентификация (неверный пароль, ключ, метод и т. д.). Классический брутфорс. Каждый провал добавляет 5 секунд штрафа.
noauth:1 — клиент подключился, не предпринял вообще никаких действий по аутентификации и отключился. Типичное поведение сканеров и «шумов» интернета.
grace-exceeded:10 — клиент не успел пройти аутентификацию за время LoginGraceTime (по умолчанию 120 секунд). Добавляет 10 секунд штрафа.
refuseconnection:10 — сервер сам отказал в соединении (например, из-за превышения MaxStartups). Также 10 секунд.
Пороги срабатывания
min:15 — минимальный накопленный штраф, при котором IP начинает блокироваться. Пока сумма штрафов меньше 15 секунд — соединения принимаются нормально. Это защищает от случайных единичных ошибок.
max:600 — максимальный штраф (10 минут). Даже если клиент «наберёт» больше, штраф не превысит это значение.
Ограничения по количеству отслеживаемых источников
max-sources4:65536 — максимальное количество IPv4-адресов, за которыми одновременно следит sshd.
max-sources6:65536 — то же для IPv6.
Значения по умолчанию очень высокие — по сути, «отслеживать всех».
Поведение при переполнении таблицы
overflow:permissive
overflow6:permissive
Когда количество отслеживаемых источников превышает лимит, сервер продолжает добавлять новые записи, но чтобы освободить под них место в таблице, выкидывает наименее заштрафованные старые. В исходниках можно увидеть красноречивый комментарий:
/* Delete the soonest-to-expire penalties. */
Сначала я не правильно понял, как работает этот механизм, но в комментах меня поправили. Спасибо за это @firegurafiku
PerSourcePenalties не является заменой Fail2Ban. В отличие от него, механизм не банит IP полностью, а временно замедляет или блокирует подключения пропорционально степени «виновности» источника (если буфер не переполнен).
Как работает PerSourcePenalties
Отказ в соединении инициируется самим процессом sshd (на уровне приложения), а не через правила брандмауэра (iptables/nftables).
Механизм работает следующим образом:
Принятие соединения: основной процесс sshd принимает входящее TCP-соединение (выполняет accept()).
Проверка таблицы: сразу после принятия соединения sshd проверяет IP-адрес клиента по своей внутренней таблице штрафов. PerSourcePenalties хранит состояние во внутренней памяти master-процесса sshd, поэтому штатного способа посмотреть текущее содержимое таблицы нет.
Немедленный разрыв: если накопленный штраф для этого IP превышает установленный порог, sshd просто закрывает сокет, не запуская дочерний процесс для аутентификации.
Почему это сделано именно так?
Автономность. Функциональность доступна «из коробки» на любой операционной системе (Linux, BSD, macOS) без необходимости иметь права root для модификации правил фильтрации пакетов.
Безопасность. Поскольку основной процесс sshd теперь не выполняет сложную логику парсинга данных от клиента (этим занимается отдельный sshd-session), быстрая проверка IP по списку штрафов практически не потребляет ресурсов и защищает от перегрузки.
Отсутствие интеграции с ядром. В отличие от того же Fail2Ban, OpenSSH не вызывает внешние команды вроде iptables -A.... Это быстрее и исключает риск того, что ошибка в скрипте заблокирует доступ ко всему серверу.
Важное замечание. PerSourcePenalties работает по IP. А значит, офисы, VPN, мобильные операторы, CGNAT могут случайно «наказывать» сразу группу пользователей.
Рубрика «э-э-эксперименты»
Давайте протестируем, как это работает на практике. Я попробую подключиться с неверным паролем несколько раз подряд и буду смотреть логи:

Кстати, в обычном логе этого не видно. Мне пришлось добавить настройку LogLevel VERBOSE в конфиг /etc/ssh/sshd_config.
При настройках по умолчанию я вообще не заметил работу этой фичи, когда пытался брутфорсить вручную. То есть можно сделать вывод, что пользователь сам себя не заблочит при неправильном вводе своего пароля (Напоню, что вход по паролю это не самая хорошая идея. Настройте ключи и ходите по ним).
Но вручную никто такого не делает, так что автоматизируем. Я запустил Hydra на стандартных значениях и с rockyou:

Смотрим логи в терминале “жертвы”:

На первом этапе Hydra создала большое количество соединений без попытки аутентификации. За это IP получил минимальный штраф:
srclimit_penalise: ipv4: new х.х.х.11/32 deferred penalty of 1 seconds - это noauth штраф (1 секунда)
for penalty: connections without attempting authentication
Далее сервер начал отбрасывать избыточные соединения по лимиту MaxStartups.

Затем, когда соединения дошли до стадии ввода пароля, начали накапливаться штрафы за failed authentication. Критический момент наступил, когда накопленный штраф превысил порог:

srclimit_penalise: х.х.х.11/32: activating ipv4 penalty of 19 seconds - накопленный штрафПосле этого все новые соединения от этого IP стали отбрасываться с сообщением
drop connection ... penalty: failed authentication.

Важный нюанс: С точки зрения атакующего это выглядит как «Connection closed by remote host» сразу после установки TCP-сессии.
Сравнение с Fail2Ban: когда что использовать
Многие администраторы сразу вспоминают Fail2Ban, когда речь заходит о защите SSH. Давайте сравним два подхода.
Параметр |
PerSourcePenalties (OpenSSH) |
Fail2Ban |
Место работы |
Встроено в sshd |
Внешний демон, парсит логи |
Скорость реакции |
Мгновенная (в момент события) |
Задержка (зависит от частоты проверки логов) |
Тип блокировки |
Временная, динамическая (секунды — десятки минут) |
Обычно жёсткая и долгая (минуты — сутки) |
Ресурсоёмкость |
Очень низкая |
Средняя и выше (постоянный мониторинг логов) |
Гибкость штрафов |
Высокая (разные штрафы за разные нарушения) |
Высокая, но требует написания фильтров |
Защита от DoS на саму защиту |
Есть (overflow:permissive) |
Зависит от конфигурации |
Постоянные баны |
Не сделать |
Легко и привычно |
Если смотреть на плюсы обоих подходов, то вот что можно выделить.
Работает из коробки и включена по умолчанию в Ubuntu 26.04.
Не требует парсинга логов — реакция быстрее и надёжнее.
Значительно меньше ложных срабатываний на легитимных пользователей (особенно тех, кто случайно ошибся с паролем).
Устойчив к атакам на сам механизм защиты.
Минимальное потребление ресурсов.
Преимущества Fail2Ban
Более привычный и "видимый" инструмент.
Можно банить навсегда или на очень долгий срок.
Работает с любыми сервисами.
Легко интегрируется с iptables/nftables, Fail2Ban-ботами в Telegram, Cloudflare и т.д.
Богатая экосистема готовых jail'ов.
Отключать механизм полностью я бы не рекомендовал, но если это всё же необходимо, можно создать отдельный override-конфиг:
/etc/ssh/sshd_config.d/99-pensourcepenalties-disable.conf
Имя файла может быть любым. Числовой префикс определяет порядок загрузки конфигурации.
Полностью отключить механизм можно директивой:
PerSourcePenalties no
Либо отключить только отдельные типы штрафов:
PerSourcePenalties authfail:0 noauth:0
После изменения конфигурации не забудьте перезапустить sshd:
systemctl restart ssh
Выводы и полезный совет
PerSourcePenalties не заменяет Fail2Ban полностью, а отлично дополняет его, как первая линия обороны (быстрая реакция на шум и лёгкий брутфорс).
Fail2Ban — отлично подходит на роль второй линии (долгие баны за упорные атаки, защита других сервисов, удобный мониторинг).
После тестов у меня не появилось ощущения, что PerSourcePenalties должен заменить Fail2Ban. Но теперь SSH-сервер хотя бы умеет самостоятельно отбиваться от самого примитивного шума и брутфорса без внешних костылей. И, честно говоря, для встроенного механизма это работает неожиданно неплохо. Что скажете?
Комментарии (16)

gotch
07.05.2026 17:27Если стоит как в Azure по умолчанию - аутентификация только по ключу, то всё это не актуально?
И хотел ещё уточнить - а за сколько не продаёте?

aborouhin
07.05.2026 17:27аутентификация только по ключу, то всё это не актуально?
Так мы ж защищаемся не от того, что кто-то реально подберёт пароль (у тех, кто дошёл до настройки подобных механизмов, в любом случае или только по ключу, или пароль, который подбирать вечность), а от того, чтобы долбящиеся в надежде подобрать пароль боты нам сервер не нагружали. А они всё равно долбятся и пытаются авторизоваться по паролю, даже если сервер принимает только ключи.

j_larkin Автор
07.05.2026 17:27Нет, аутентификация по ключу спасает от того, что пароль всё таки подберут. Но пытаться всё равно будут, так как снаружи не видно пароль у Вас там или ключи.
Уточняю, ни за сколько не продаю =)
gotch
07.05.2026 17:27Ясно, спасибо за объяснение и за статью. Есть повод теперь её внимательно прочитать.

Anselm_nn
07.05.2026 17:27снаружи таки видно, но некоторые тупые боты все равно долбятся, даже если ssh сервер явно отвечает, что только по ключу)

firegurafiku
07.05.2026 17:27Когда количество отслеживаемых источников превышает лимит, сервер переходит в «мягкий» режим: перестаёт добавлять новые IP в таблицу штрафов, но не блокирует их. Это предотвращает DoS-атаку на сам механизм защиты. Эффективность механизма при этом значительно снижается.
По-моему, у вас тут фактическая ошбка: тут утверждается, что при заполнении таблицы новые соединения автоматически разрешаются. Но давайте посмотрим документацию:
There are two operating modes: deny-all, which denies all incoming connections other than those exempted via PerSourcePenaltyExemptList until a penalty expires, and permissive, which allows new connections by removing existing penalties early (default: permissive).
То есть, это нет так — сервер продолжает добавлять новые записи, но чтобы освободить под них место в таблице, выкидывает наименее заштрафованные старые. Можно даже заглянуть в исходники и увидеть там красноречивый комментарий:
/* Delete the soonest-to-expire penalties. */
j_larkin Автор
07.05.2026 17:27Спасибо за уточнение, поправил этот момент в статье. Карму, к сожалению, начислить не дает. По этому могу только плюсануть.

aborouhin
07.05.2026 17:27Ещё одна мысль возникла - это же дело надо как-то мониторить (и просто статистики ради, и чтобы отлавливать аномальные всплески). Для fail2ban есть экспортер, а тут что-то сходу ничего готового из коробки не нашёл. Хотя написать тривиально, если формат лога стабильный. Можно даже не полноценный экспортер, а скриптик, который пишет файл для textfile collector node-exporter'а.

Aule
07.05.2026 17:27Сервис можно, он в journal пишет события, а syslog-ng разбирай как хочешь, ну или rsyslog настроить логика та же
[Unit]Description=Forward SSH penalty events to central syslogAfter=network.target sshd.service[Service] Type=simple ExecStart=/bin/sh -c 'journalctl -u sshd -f --output=cat -n0 | grep --line-buffered "activating.*penalty" | nc logs.company.ru 514' Restart=always RestartSec=5 User=root[Install] WantedBy=multi-user.target
aborouhin
Скорее уж overflow:deny-all плюс фиксированный IP для аварийного доступа в PerSourcePenaltyExemptList
А permissive, если я правильно понял, при достаточно мощной атаке как раз позволит пробить PerSourcePenalty и загрузить уже основной процесс sshd, так что и администратор не пробьётся.
j_larkin Автор
Спасибо за комментарий. Думаю, что при достаточно мощной атаке не только эта фича не поможет, а, в принципе, ничего не спасет. Но, как мне кажется, критичные сервера, на которые проводят мощные атаки, не святят ssh наружу. По крайне мере, не должны светить.
aborouhin
Вообще бы с этим вопросом разобраться, если уж использовать PerSourcePenalty. Сейчас перечитал man sshd_config и, скорее всего, да, я изначально понял этот механизм неправильно.
Режим overflow включается, если количество IP (а не подключений!) превышает max-sources4 / max-sources6, которые каждый по дефолту 65536. Что-то у меня подозрения, что при атаке с такого количества адресов средний сервер действительно умрёт куда раньше, чем до этого overflow, какой бы режим ни был выбран, вообще дойдёт дело. Да и вообще, для нас простых людей гораздо реалистичнее сценарий, когда несколько сошедших с ума ботов долбятся с одних и тех же IP каждые N миллисекунд, чем когда идёт DDoS с тысяч IP, да ещё и именно на SSH. Первое у себя видел, от второго Бог пока миловал :)
А вот есть ли механизм защиты от перегрузки процесса при большом количестве одновременных соединений (а не IP)? Я такого не нашёл. Получается, что если sshd-session захлебнётся, мы теряем пациента. А захлебнётся он, IMHO, всё-таки быстрее, чем nftables, работающий в kernel space. Так что ещё один плюсик в карму fail2ban (помимо того, что он нам, конечно, и для других jail'ов всё равно нужен)
j_larkin Автор
можно попробовать на тестовом стенде запустить многопоточный брутфорс при чем с нескольких IP. Но это нужно делать когда сервера физические. Намоем стенде все виртуалки, не думаю, что при таком сетапе эксперимент будет чистым. Так как брут сам по себе будет нагружать ЦПУ, и это может сказаться на подопытной ВМ.
Я, кстати, не уверен, что такую нагрузку можно сгенерировать таким способом. Хотя идея привлекательная. Было бы интересно такое реализовать.
Ну и как я писал в статье, наличие этой фичи, не означает, что f2b теперь не нужен.
aborouhin
У меня есть физические (собственно, только на них SSH наружу и торчит), но я не настолько заинтересовался вопросом, чтобы так заморочиться :)
j_larkin Автор
Понимаю. У меня просто спортивный интерес. Я из пачнота сначала вообще не понял про что речь. Потом погуглил немного и не нашёл чего-то, что объяснило весь функционал. Решил разобраться. Так статья и появилась.