MAATRIX / Блог / Потолок SSH-сессий: сколько человек зайдёт одновременно и что упадёт раньше

Потолок SSH-сессий: сколько человек зайдёт одновременно и что упадёт раньше

MAATRIX

Команда разработчиков заводит общий дев-сервер: у каждого свой пользователь, свой tmux, свой редактор в терминале — обычная схема для небольшой команды без Kubernetes и отдельного стенда на каждого. Работает нормально, пока однажды в конце спринта, когда все одновременно деплоят и смотрят логи, кто-то не может законнектиться, а у тех, кто уже внутри, всё начинает тормозить. Вопрос «сколько человек вообще может держать SSH-сессию на этом сервере» звучит просто, но ответ зависит не от одной цифры в конфиге sshd, а от того, что на сервере закончится раньше — свободные слоты подключения или свободная память. Разберём оба потолка по отдельности и посчитаем, как их соотнести для конкретной команды.

Что вообще ограничивает sshd: MaxSessions и MaxStartups

У sshd есть два параметра, которые часто путают, хотя они ограничивают разные вещи.

MaxSessions — это число одновременных сессий (shell, sftp, port-forward) внутри одного уже установленного TCP-соединения. По умолчанию — 10. На практике это почти никогда не узкое место для обычного пользователя: один человек с одного клиента редко открывает больше пары вкладок терминала через мультиплексирование одного соединения. Этот лимит защищает от злоупотребления одним каналом, а не от наплыва пользователей.

MaxStartups — куда более значимый параметр. Он ограничивает число *неаутентифицированных* соединений, то есть тех, что уже сделали TCP-хендшейк, но ещё не прошли аутентификацию. Формат — start:rate:full, например 10:30:100: до 10 подключений в процессе аутентификации обрабатываются как обычно, дальше с вероятностью 30% и растущей до 100% при достижении 100 новые попытки начинают отклоняться случайным образом. Это защита от брутфорса и SYN-флуда на порт 22, а не бюджет на легитимных пользователей — команда из 10–20 разработчиков, которые заходят по ключу, крайне редко упирается в этот лимит, если только не разворачивает массовый параллельный деплой через SSH с сотнями короткоживущих соединений одновременно.

Ключевой вывод: дефолтные значения sshd рассчитаны на защиту от атак, а не на пропускную способность обычной команды. Если вы не трогали sshd_config, sshd почти никогда не станет первой причиной отказа — раньше упрётся сама система. Подробно о том, что происходит на каждом шаге подключения от TCP до приглашения shell, разобрано в статье про этапы SSH-подключения — полезно, если нужно понять, на каком именно этапе сессия зависает.

Сколько на самом деле стоит одна интерактивная сессия

Каждая новая SSH-сессия — это не одна абстрактная «единица нагрузки», а конкретная цепочка процессов и системных вызовов:

  • родительский sshd слушает порт, на новое соединение форкает дочерний процесс;
  • дочерний sshd проходит цепочку PAM-модулей (аутентификация, лимиты, создание сессии);
  • выделяется псевдотерминал (pty/tty);
  • запускается login shell пользователя — bash или zsh — который читает .bashrc/.zshrc, инициализирует historian, completion, возможно prompt-фреймворк вроде oh-my-zsh;
  • если это не голая сессия, а рабочая — сверху добавляются tmux/screen, редактор, языковой сервер, интерпретатор, запущенные сборки.

Голая оболочка без ничего — это единицы мегабайт памяти и почти нулевая нагрузка на CPU в простое. Но в реальной работе разработчика сессия почти никогда не остаётся голой: тяжёлый zsh-фреймворк, tmux с несколькими панелями, открытый в терминале Vim или Neovim с LSP-сервером под конкретный язык, npm/cargo/go build, запущенный в фоне — каждый такой элемент добавляет десятки, а то и сотни мегабайт памяти на пользователя, и это уже не фиксированная цифра, а зависит от стека и привычек команды. Именно поэтому точный «вес одной SSH-сессии в мегабайтах» указать нельзя — можно только посчитать по факту для конкретной команды и конкретного тулинга (см. раздел про расчёт лимита ниже).

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Что упадёт раньше: sshd или система

Здесь два разных сценария отказа, и они выглядят и лечатся по-разному.

Отказ на уровне sshd. Новое подключение получает kex_exchange_identification: read: Connection reset by peer или зависает на этапе аутентификации и в итоге получает таймаут. Это значит, что упёрлись именно в MaxStartups (шквал параллельных попыток подключения) или, реже, в MaxSessions при активном мультиплексировании одного соединения. Уже подключённые пользователи при этом ничего не замечают — их сессии работают как обычно, проблема только у тех, кто пытается зайти впервые.

Деградация на уровне системы. Все, кто уже внутри, начинают ощущать лаги: команды выполняются с задержкой, top показывает нагрузку на CPU выше числа ядер, free показывает, что свободной памяти почти не осталось и активно используется swap. В худшем случае вмешивается OOM killer, который начинает убивать процессы, чтобы освободить память, и жертвой может стать как чья-то сессия, так и рабочий сервис на том же сервере. Новые подключения при этом технически проходят TCP и SSH-хендшейк, но shell пользователя запускается за секунды или не запускается вовсе, потому что системе не хватает памяти форкнуть очередной процесс.

Для типичного дев-сервера с командой в 10–30 человек и дефолтными настройками sshd деградация системы наступает раньше, чем отказ sshd — просто потому, что лимиты sshd рассчитаны на защиту от сотен паразитных подключений в секунду, а реальная команда даёт единицы-десятки легитимных сессий. Исключение — если кто-то намеренно (или по ошибке в скрипте CI) открывает много параллельных короткоживущих SSH-соединений за раз: тогда можно упереться именно в MaxStartups, не успев исчерпать память.

PAM и другие невидимые тормоза

Между TCP-соединением и приглашением shell стоит стек PAM (Pluggable Authentication Modules), и он добавляет не только защиту, но и издержки, которые редко учитывают при расчёте потолка:

  • pam_unix.so / pam_sss.so / pam_ldap.so — если аутентификация идёт не по локальному /etc/passwd, а через LDAP или SSSD (типично для корпоративных серверов с центральным каталогом пользователей), каждый логин — это сетевой запрос к внешнему сервису. Под нагрузкой (массовый логин утром или после сбоя VPN) это может стать локальным узким местом ещё до того, как заполнится память.
  • pam_limits.so — читает /etc/security/limits.conf и применяет лимиты nproc/nofile к сессии. Если лимит на число процессов на пользователя стоит слишком низко, а разработчик в своей сессии открывает несколько вкладок tmux с фоновыми процессами, можно упереться в лимит процессов даже при свободной памяти на сервере — история именно такого инцидента разобрана в статье про лимит процессов, который обрубил деплой.
  • pam_systemd.so — регистрирует сессию в systemd-logind, создаёт cgroup-слайс user-<uid>.slice. Это же используется современными дистрибутивами для лимитирования ресурсов конкретного пользователя через systemd, а не только через классический limits.conf.
  • journald — каждое подключение и его закрытие логируются; при очень частых коротких сессиях (например, скрипт мониторинга, который логинится каждую минуту) это добавляет заметный, хоть и небольшой, постоянный фон записи на диск.

Ни один из этих шагов сам по себе не «роняет» сервер при разумном числе пользователей, но в сумме они формируют задержку старта каждой новой сессии, которая растёт нелинейно при одновременном наплыве логинов — то, что называют login storm, типично после перезагрузки сервера или после массового restart VPN.

Как посчитать безопасный лимит для команды разработчиков

Прямой формулы «сервер с N ГБ памяти держит M пользователей» не существует — слишком сильно варьируется вес одной сессии от стека и привычек команды. Но можно посчитать разумный ориентир по шагам.

  1. Оставьте память под системные нужды и приложения. Если на сервере крутятся ещё и сервисы (БД, приложение, мониторинг), вычтите их пиковое потребление и оставьте резерв под page cache и буферы — не выделяйте под интерактивные сессии всю физическую память сервера.
  2. Оцените вес типовой сессии эмпирически, а не по паспортным цифрам. Попросите пару человек из команды поработать в обычном режиме (редактор, tmux, локальная сборка) и посмотрите фактическое потребление их сессий через ps или smem — у голой оболочки и у сессии с открытым Neovim и LSP разница может быть в разы, и только замер на вашем стеке даёт реальную цифру.
  3. Заложите не 100%, а реалистичный процент одновременной активности. Не вся команда сидит в терминале сервера одновременно даже в рабочие часы — часть пишет код локально и заходит на сервер эпизодически. Какой процент актуален именно для вашей команды, лучше зафиксировать по факту наблюдения (например, через who/w в течение недели), а не закладывать заранее произвольное число.
  4. Добавьте запас на пиковые события. Утренний вход всей команды разом, массовый деплой, инцидент, когда все одновременно смотрят логи — это моменты, когда реальная одновременная нагрузка выше обычной, и именно они должны определять потолок, а не средний день.

Общий подход для роста нагрузки — не только по SSH-сессиям, но и по другим типам одновременных пользователей — разобран в статье про то, что ломается на разных порядках числа пользователей: она полезна, чтобы понимать, какая метрика становится узким местом первой на каждом следующем масштабе.

Если по расчёту получается, что дев-серверу нужно держать заметно больше одновременных интерактивных сессий, чем позволяет текущая память, дешевле и надёжнее увеличить память или ядра под сервер, чем городить сложную схему приоритезации пользователей на одной небольшой машине.

Настройка лимитов: sshd, PAM и cgroups

Три уровня, на которых имеет смысл выставлять защитные лимиты — сам sshd, классический limits.conf и современные cgroup-лимиты systemd.

sshd (/etc/ssh/sshd_config) — защита от переполнения на входе:

MaxSessions 10
MaxStartups 30:50:150
ClientAliveInterval 60
ClientAliveCountMax 3
LoginGraceTime 30

ClientAliveInterval/ClientAliveCountMax важны отдельно: они не про число сессий, а про то, чтобы «зависшие» соединения (закрытый ноутбук, оборвавшийся VPN) не висели вечно, занимая ресурсы — sshd будет пинговать клиента и рвать соединение, если тот не отвечает.

Лимиты процессов и дескрипторов (/etc/security/limits.conf) — защита от того, чтобы один пользователь исчерпал общесистемный ресурс:

@developers soft nproc  512
@developers hard nproc  1024
@developers soft nofile 4096
@developers hard nofile 8192

Важная деталь, которую легко упустить: эти лимиты применяются только если в /etc/pam.d/sshd подключён pam_limits.so — без этой строки limits.conf просто игнорируется для SSH-сессий. Про сами файловые лимиты и типичные ошибки с ними — в статье про настройку ulimit для открытых файлов.

Cgroup-лимиты через systemd — более современный и точный способ ограничить именно интерактивные сессии пользователя по памяти и CPU, не трогая системные сервисы. В /etc/systemd/system/user-.slice.d/override.conf (или конкретно под user-1000.slice для отдельного пользователя):

[Slice]
MemoryMax=2G
TasksMax=200
CPUQuota=150%

Это ограничивает совокупно все процессы конкретного пользователя, включая все его SSH-сессии, tmux-панели и фоновые сборки — что не даёт одному человеку с забытым в фоне процессом уронить сервер остальным. Проверить активные сессии и их владельцев:

loginctl list-sessions
who -u
ss -tnp | grep ':22'
systemd-cgls /user.slice

Последняя команда особенно полезна для быстрой диагностики «кто ест ресурсы» — она показывает дерево cgroup с процессами каждого пользовательского слайса, что быстрее, чем разбирать вывод ps aux по PID.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.

Частые вопросы

Что изменится, если просто поднять MaxStartups до огромного значения?

Ничего хорошего само по себе — вы уберёте защиту от переполнения таблицы неаутентифицированных соединений, но не добавите памяти или CPU системе. Если узкое место — не sshd, а ресурсы, поднятие MaxStartups просто переносит момент отказа с «явного отказа в подключении» на «система тормозит для всех уже подключённых».

Можно ли ограничить конкретного пользователя, не трогая остальных?

Да, именно для этого и нужны cgroup-слайсы systemd (user-<uid>.slice) или классический limits.conf с блоками по группам — оба способа позволяют дать разным ролям (разработчик, CI-бот, читатель логов) разные лимиты на одном сервере.

Стоит ли ограничивать число сессий на одного пользователя отдельно от общего числа пользователей?

Да, если люди привыкли открывать много параллельных SSH-соединений (например, по одному на каждую вкладку терминала вместо мультиплексирования через tmux) — MaxSessions на уровне одного TCP-соединения этого не ловит, а вот cgroup-лимит по TasksMax или явный контроль через мониторинг — ловит.

Помогает ли банальное увеличение MaxSessions решить проблему нехватки слотов?

Только если узкое место реально в этом параметре, а не в памяти или в PAM-цепочке — прежде чем менять конфиг sshd, стоит посмотреть free -h и loginctl list-sessions в момент проблемы, чтобы понять, что именно упёрлось в потолок.

Нужно ли беспокоиться об этом на сервере с 3-5 разработчиками?

На таком масштабе дефолтные настройки sshd почти никогда не станут проблемой, а вот память стоит прикинуть заранее, если в команде принято держать открытыми тяжёлые IDE-сессии, LSP-серверы и локальные сборки прямо на сервере, а не только редактировать код и запускать команды.

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →