Потолок SSH-сессий: сколько человек зайдёт одновременно и что упадёт раньше
Команда разработчиков заводит общий дев-сервер: у каждого свой пользователь, свой 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 пользователей» не существует — слишком сильно варьируется вес одной сессии от стека и привычек команды. Но можно посчитать разумный ориентир по шагам.
- Оставьте память под системные нужды и приложения. Если на сервере крутятся ещё и сервисы (БД, приложение, мониторинг), вычтите их пиковое потребление и оставьте резерв под page cache и буферы — не выделяйте под интерактивные сессии всю физическую память сервера.
- Оцените вес типовой сессии эмпирически, а не по паспортным цифрам. Попросите пару человек из команды поработать в обычном режиме (редактор, tmux, локальная сборка) и посмотрите фактическое потребление их сессий через
psилиsmem— у голой оболочки и у сессии с открытым Neovim и LSP разница может быть в разы, и только замер на вашем стеке даёт реальную цифру. - Заложите не 100%, а реалистичный процент одновременной активности. Не вся команда сидит в терминале сервера одновременно даже в рабочие часы — часть пишет код локально и заходит на сервер эпизодически. Какой процент актуален именно для вашей команды, лучше зафиксировать по факту наблюдения (например, через
who/wв течение недели), а не закладывать заранее произвольное число. - Добавьте запас на пиковые события. Утренний вход всей команды разом, массовый деплой, инцидент, когда все одновременно смотрят логи — это моменты, когда реальная одновременная нагрузка выше обычной, и именно они должны определять потолок, а не средний день.
Общий подход для роста нагрузки — не только по 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 ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →