Прямой ответ
SSH-сервер с паролем и открытым 22-м портом — самая сканируемая цель интернета (тысячи попыток подбора в сутки — посмотрите свой auth-лог). Базовый hardening закрывает 99% риска и делается за полчаса: только ключи (PasswordAuthentication no — перебор паролей становится невозможен как класс), без root-логина (PermitRootLogin no — вход под обычным пользователем, sudo явно), ограниченный список пользователей (AllowUsers), ключи — ed25519 с passphrase (агент хранит разблокированный ключ, компрометация файла ключа без passphrase бесполезна). Следующий уровень — убрать SSH из интернета вовсе: доступ только из WireGuard-сети или через jump-хост, и публичного 22-го порта не существует. И обязательная страховка от самострела: вторая сессия открыта во время правки sshd_config + проверенный резервный канал (консоль провайдера), потому что «сохранил конфиг с опечаткой — и сервер недоступен» происходит регулярно.
Ключи: генерация, типы, гигиена
Генерация: ssh-keygen -t ed25519 -a 100 -C "user@laptop-2026" (ed25519 — современный стандарт: короче и быстрее RSA при большей стойкости; RSA 4096 — для легаси-совместимости; -a 100 — раунды KDF, дороже брутфорс украденного файла). Passphrase — обязателен для ноутбуков (файл ключа утекает вместе с машиной/бэкапом; passphrase превращает кражу файла в бесполезную), агент (ssh-agent/systemd-user-агент/macOS Keychain) разблокирует один раз за сессию. Раскладка: ~/.ssh/config на клиенте — алиасы хостов, пользователи и ключи по хостам (вместо длинных команд), IdentityFile на каждый хост свой (один ключ на всё — компрометация одного сервера открывает остальные). Публичный ключ — в authorized_keys сервера (ssh-copy-id); права — параноидальные (~/.ssh 700, authorized_keys 600, домашний каталог без group-write — sshd молча отказывает при свободных правах, и «ключ не работает» обычно про это). Ротация/отзыв: приватный ключ скомпрометирован или сотрудник ушёл — строка из authorized_keys удаляется (минутное дело, если доступы централизованы — ansible-роль управляет authorized_keys декларативно).
sshd_config: строки, которые имеют значение
Правки в /etc/ssh/sshd_config (или лучше — drop-in /etc/ssh/sshd_config.d/hardening.conf): PermitRootLogin no (root — только через sudo с явным следом в логах; prohibit-password — компромисс, если root-ключ нужен автоматизации); PasswordAuthentication no и KbdInteractiveAuthentication no (только после проверки, что ключевой вход работает!); AllowUsers deploy admin (явный список — новый пользователь не появится в SSH «сам»); MaxAuthTries 3 (меньше попыток на соединение); ClientAliveInterval 300/ClientAliveCountMax 2 (мёртвые сессии закрываются); X11Forwarding no (не используется — не должно быть включено). Применение: sshd -t (проверка синтаксиса — обязательна до reload!) и systemctl reload sshd при живой второй сессии; если всё сломалось — консоль провайдера (out-of-band доступ проверяется до hardening'а, а не после). Отдельно: AllowTcpForwarding/GatewayPorts — по необходимости (туннели — легитимная практика, гайд, но gateway-режим наружу по умолчанию не нужен).
Fail2ban, порт и сетевой уровень
Fail2ban — бан по логам auth: после N провалов IP уходит в файрвол на час; с ключевой аутентификацией его ценность ниже (подбирать нечего), но остаётся шум в логах и защита от перебора passphrase — ставить по инерции полезно (jail sshd из коробки). Нестандартный порт (Port 22222) — не безопасность, а гигиена: автоматические сканеры исчезают из логов (адресные атаки порт не останавливает); допустим как косметика, но не как мера. Настоящий сетевой уровень — убрать доступ: файрвол с default-deny и 22-й порт только для VPN-подсети (WireGuard-сценарий — эталон: из интернета SSH не виден вовсе), или jump/bastion-хост (единственная точка входа с усиленной защитой, целевые серверы принимают соединения только с него). Для командного доступа поверх — централизованная выдача ключей (ansible) и, при росте, SSH-CA (подписанные сертификаты вместо authorized_keys на каждом хосте — отзыв одной подписью).
Практический сценарий: новый VPS за час
Чек-лист первого часа жизни сервера (порядок важен). (1) Вход под root от провайдера; создание пользователя deploy с ключом (ssh-copy-id) и sudo; проверка входа под deploy до закрытия root-доступа. (2) Hardening drop-in: PermitRootLogin no, PasswordAuthentication no, AllowUsers deploy; sshd -t, reload при живой сессии deploy; проверка нового подключения. (3) Файрвол: ufw/nftables default-deny, разрешены 22, 80, 443 (диагностика подтверждает, что ничего лишнего не слушает — ss -tlnp). (4) Fail2ban, unattended-upgrades (патчи OpenSSH приходят сами — незакрытая CVE в sshd при ручных обновлениях «когда-нибудь» — классика). (5) Когда поднят WireGuard — сужение 22-го до VPN-подсети и удаление из публичного файрвола. (6) Всё перечисленное — ansible-плейбуком (роль ssh-hardening): следующий сервер получит настройку прогоном, а не памятью автора. Итог: auth-лог чистый (сканеры не достают), вход — только с двух ноутбуков с passphrase-ключами, root-доступа из сети нет.
FAQ
Безопасно ли держать passphrase-ключ в агенте?
Агент хранит разблокированный ключ в памяти сессии: риск — локальный рут на вашей машине (тогда всё потеряно anyway), зато украденный файл ключа без passphrase бесполезен. Баланс в пользу «passphrase + агент»; для серверных ключей автоматизации (не интерактивных) — отдельные ключи без passphrase с жёстким ограничением command=/from= в authorized_keys.
Что делать, если закрыл себе доступ?
Консоль провайдера (VNC/serial) — грузимся в single-user/через веб-консоль, правим конфиг; на собственном железе — физический доступ/recovery-загрузка. Профилактика: вторая сессия при правках, sshd -t до reload, и проверенный out-of-band канал до начала hardening'а.
Нужен ли двухфакторный SSH?
Для серверов с критичным доступом — да (TOTP через pam_oath/google-authenticator, или FIDO2-ключи — OpenSSH поддерживает sk-ключи -t ed25519-sk нативно: аппаратный ключ обязателен физически). Для личного VPS за VPN — обычно избыточно: ключ+passphrase+VPN уже три слоя.
Как пускать CI/деплой по SSH без пароля человека?
Отдельный deploy-ключ (без passphrase, живёт в секретах CI — управление секретами) с ограничением в authorized_keys: command="/opt/deploy/run.sh",no-port-forwarding,no-X11-forwarding — ключ умеет ровно запускать деплой-скрипт и больше ничего; отзыв — удаление строки.
Источники: man sshd_config, ssh-keygen (OpenBSD), документация fail2ban — ссылки в шапке.