Защита SSH: вход по ключу, запрет root-логина, отключение пароля
ed25519 в Linux, macOS и Windows, ловушка cloud-init в /etc/ssh/sshd_config.d, активация через сокет в Ubuntu 24.04, доказательство через sudo sshd -T и аварийный путь через консоль.
Свежеустановленный сервер попадает в списки сканеров уже через несколько минут после того, как впервые становится доступен из сети. Прилетает при этом почти всегда одно и то же: автоматический перебор имён пользователей и паролей на порту 22. Тот, кто отключает вход по паролю и оставляет только ключи, лишает весь этот класс атак почвы под ногами. Не ослабляет, а убирает полностью.
Базовые шаги известны почти всем. В обычных руководствах не хватает того, что идёт дальше: почему PasswordAuthentication no на облачных образах регулярно остаётся без эффекта, почему systemctl reload ssh на сервере с активным ssh.socket делает совсем не то, что вы ожидаете, и как доказать, а не понадеяться, что изменение вступило в силу. Именно об этом пойдёт речь. Все сведения относятся к Debian 13, Debian 12, Ubuntu 24.04 и Ubuntu 22.04.
Правило, которое спасает всё: два сеанса
Прежде чем менять хоть что-то в конфигурации SSH, откройте второе окно терминала и войдите в нём на сервер ещё раз. Этот второй сеанс остаётся открытым до тех пор, пока вы успешно не проверите новую конфигурацию третьим, полностью новым соединением.
Причина техническая: перезапуск службы SSH не разрывает уже установленные сеансы. Активные соединения обслуживаются дочерними процессами, порождёнными раньше, и переживают перезапуск родительского процесса. Сломанную конфигурацию вы заметите только при следующем подключении, и тогда старый сеанс окажется вашим единственным путём назад. Тот, кто закрывает его, чтобы «зайти заново начисто», жалеет об этом с завидной регулярностью.
Проверьте заодно версию OpenSSH, потому что от неё зависит сразу несколько деталей:
ssh -V
| Система | OpenSSH | Слушатель по умолчанию |
| Debian 13 (trixie) | 10.0p2 | ssh.service |
| Debian 12 (bookworm) | 9.2p1 | ssh.service |
| Ubuntu 24.04 LTS | 9.6p1 | ssh.socket |
| Ubuntu 22.04 LTS | 8.9p1 | ssh.service |
Если серверной службы нет вообще, например в минимальном образе, доустановите её:
sudo apt update
sudo apt install -y openssh-server
Создание ключа: ed25519 в Linux, macOS и Windows
Берите ed25519. Короткий, быстро проверяется, большой запас прочности, и его понимает каждая рассмотренная здесь версия OpenSSH. RSA нужен только для старых систем, которые не принимают ничего другого, и тогда с длиной не менее 4096 бит.
Linux и macOS
ssh-keygen -t ed25519 -a 100 -C "hani@notebook" -f ~/.ssh/id_ed25519
-a 100 повышает число раундов KDF для парольной фразы и заметно удорожает офлайновые атаки на файл ключа. -C задаёт комментарий, который потом окажется в authorized_keys и подскажет вам, какой ключ с какого устройства. Обязательно задайте парольную фразу. Ключ без парольной фразы — это просто файл, который унесёт любой, кто хоть на минуту окажется за вашим компьютером.
Чтобы не вводить парольную фразу при каждом подключении, кэшированием займётся агент:
eval "$(ssh-agent -s)" && ssh-add ~/.ssh/id_ed25519
В macOS парольная фраза вместо этого кладётся в связку ключей. Прежняя опция -K начиная с macOS 12 называется --apple-use-keychain:
ssh-add --apple-use-keychain ~/.ssh/id_ed25519
Чтобы это пережило перезагрузку, в ~/.ssh/config должно быть:
Host *
UseKeychain yes
AddKeysToAgent yes
IdentityFile ~/.ssh/id_ed25519
Windows
Windows 10 начиная с версии 1809, Windows 11 и Windows Server начиная с 2019 поставляются с клиентом OpenSSH. В PowerShell:
ssh -V
ssh-keygen -t ed25519 -a 100 -C "hani@windows"
Ключ окажется в C:\Users\ВАШЕ_ИМЯ\.ssh\. Если клиента нет, доустановите его как компонент Windows:
Add-WindowsCapability -Online -Name OpenSSH.Client~~~~0.0.1.0
Агент в Windows представляет собой системную службу, и в состоянии поставки она отключена:
Get-Service ssh-agent | Set-Service -StartupType Automatic
Start-Service ssh-agent
ssh-add $env:USERPROFILE\.ssh\id_ed25519
Перенос открытого ключа на сервер
На сервер попадает исключительно файл с расширением .pub. Файл без расширения — это закрытый ключ, и он никогда не покидает ваш компьютер.
Удобный способ в Linux
ssh-copy-id -i ~/.ssh/id_ed25519.pub root@203.0.113.10
ssh-copy-id создаёт ~/.ssh, выставляет правильные права, дописывает ключ в authorized_keys и заодно проверяет, нет ли его там уже. В macOS эта утилита входит в комплект не в каждой версии. Быстро убедитесь командой command -v ssh-copy-id, а иначе переходите к ручному способу.
Способ для Windows без ssh-copy-id
Клиент OpenSSH от Microsoft не содержит ssh-copy-id. Оригинал представляет собой shell-скрипт, и его никогда не портировали. Тот же результат достигается через конвейер:
type $env:USERPROFILE\.ssh\id_ed25519.pub | ssh root@203.0.113.10 "mkdir -p ~/.ssh && chmod 700 ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"
Здесь прячется деталь, которая стоит многих часов: при передаче данных внешней программе PowerShell дописывает переводы строк в формате Windows. В authorized_keys в конце строки оказывается невидимый символ возврата каретки. Пока ключ снабжён комментарием через -C, этот символ попадает в поле комментария и ничему не мешает. Без комментария он приклеивается к блоку Base64, и вход завершается отказом без внятного сообщения. Отсюда правило: всегда задавайте комментарий, а при сомнениях один раз почистите файл на сервере.
sed -i 's/\r$//' ~/.ssh/authorized_keys
Ручной способ, который работает везде
mkdir -p ~/.ssh && chmod 700 ~/.ssh && touch ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys
Затем допишите содержимое файла .pub одной-единственной строкой. Если файл уже лежит на сервере, например потому что вы его загрузили, это делается напрямую:
cat ~/.ssh/id_ed25519.pub >> ~/.ssh/authorized_keys
После этого проверьте, что на самом деле оказалось в файле:
ssh-keygen -lf ~/.ssh/authorized_keys
Вывод перечисляет отпечаток и комментарий каждой корректной записи. Всё, что здесь не появилось, разорвано переносом строки или повреждено. Сравните отпечаток с отпечатком вашего локального ключа:
ssh-keygen -l -f ~/.ssh/id_ed25519.pub
Если оба совпадают, перенос прошёл чисто. Теперь, прежде чем что-либо отключать, проверьте, работает ли вход по ключу вообще. Дальше двигаемся только тогда, когда новое соединение проходит без запроса пароля.
Ловушка: /etc/ssh/sshd_config.d и cloud-init
Именно здесь заканчивается большинство руководств, и это самая частая причина фразы «я же отключил, а оно всё равно работает».
Начиная с Debian 11 и Ubuntu 22.04 в самом начале /etc/ssh/sshd_config стоит строка, которая подключает целый каталог. Посмотрите сами, на какой она позиции:
grep -n Include /etc/ssh/sshd_config
Во всех четырёх рассматриваемых системах строка Include стоит в начале, а не в конце. И вот та особенность OpenSSH, которой почти нет ни в одном другом конфигурационном языке: побеждает первое найденное значение, а не последнее. То, что записано в файле внутри /etc/ssh/sshd_config.d/, читается раньше главного файла и перебивает любую более позднюю строку в sshd_config.
Cloud-init использует именно этот каталог. При первом запуске он пишет /etc/ssh/sshd_config.d/50-cloud-init.conf, и в зависимости от способа развёртывания там оказывается PasswordAuthentication yes. После этого вы можете сколько угодно раз вписывать PasswordAuthentication no в sshd_config: вход по паролю останется открытым. Сначала получите общую картину:
ls -la /etc/ssh/sshd_config.d/
sudo grep -riE 'passwordauthentication|permitrootlogin|kbdinteractive' /etc/ssh/sshd_config /etc/ssh/sshd_config.d/
Отсюда следует главная рекомендация: не трогайте sshd_config вообще. Вместо этого создайте собственный файл, имя которого по алфавиту стоит перед всем, что складывает туда автоматика. Файлы читаются в отсортированном порядке, 01- идёт раньше 50-, и раз побеждает первое значение, cloud-init может потом переписывать свой файл сколько угодно, не отменяя вашу защиту. В этом отличие от широко распространённого совета создать 99-hardening.conf: такой файл проигрывает cloud-init, причём молча. Впрочем, та же механика может обернуться и против вас: файл с меньшим номером, например 00-cloud.conf, выиграет у вашего 01-, потому что OpenSSH берёт значение, прочитанное первым. Поэтому заглянуть в каталог стоит и после настройки.
Пишем правила защиты, с таймером отката в качестве страховки
Сначала постройте страховочную сеть. Следующая команда через десять минут автоматически удалит ваш новый файл и перезапустит SSH, если вы до тех пор её не отмените:
sudo systemd-run --on-active=10min --unit=ssh-rollback /bin/sh -c 'rm -f /etc/ssh/sshd_config.d/01-hardening.conf; systemctl try-restart ssh.socket ssh.service'
Теперь сама конфигурация:
sudo tee /etc/ssh/sshd_config.d/01-hardening.conf >/dev/null <<'EOF'
PubkeyAuthentication yes
PasswordAuthentication no
KbdInteractiveAuthentication no
PermitRootLogin prohibit-password
PermitEmptyPasswords no
MaxAuthTries 3
EOF
sudo chmod 644 /etc/ssh/sshd_config.d/01-hardening.conf
Две строки заслуживают пояснения. KbdInteractiveAuthentication no здесь не украшение: если оставить yes, PAM по-прежнему сможет предложить запрос пароля обходным путём через интерактивный ввод с клавиатуры, хотя PasswordAuthentication и стоит в no. Именно поэтому встречаются серверы, которые при отключённом входе по паролю всё равно спрашивают пароль.
И PermitRootLogin prohibit-password вместо no: так root-доступ по ключу сохраняется, а пароли для root исключены. Если вы завели отдельного пользователя с sudo и достоверно проверили вход по ключу для него, поставьте вместо этого PermitRootLogin no. Но не раньше.
Чего брать не стоит, хотя это и встречается в старых руководствах: ChallengeResponseAuthentication. Начиная с OpenSSH 8.7 эта опция всего лишь устаревший псевдоним для KbdInteractiveAuthentication, и в актуальных версиях она порождает в журнале сообщение об устаревании. Просто не используйте её.
Проверка синтаксиса перед активацией, без исключений:
sudo sshd -t
Пустой вывод означает: файл синтаксически в порядке. Он не означает, что по содержанию файл делает то, что вы хотите. За это отвечает sudo sshd -T чуть ниже.
Если проверка вместо этого сообщает Missing privilege separation directory: /run/sshd, значит sshd с момента запуска системы не стартовал ни разу, ведь этот каталог создаётся только вместе с ssh.service. Касается это прежде всего Ubuntu 24.04 с активацией через сокет и устраняется командой sudo mkdir -p /run/sshd или sudo systemctl start ssh.service; на сервере, где вы прямо сейчас работаете по SSH, такого всё равно не бывает.
Активация: ssh.service, ssh.socket и различия между дистрибутивами
Здесь четыре системы расходятся, и старая привычка systemctl reload ssh оказывается неверным ответом везде, где порт держит ssh.socket.
Ubuntu делает ставку на активацию через сокет начиная с 22.10, а Ubuntu 24.04 поставляет её по умолчанию: там ssh.socket включён, а ssh.service отключён. Debian с завода поступает ровно наоборот. Debian 13 и Debian 12 поставляются с включённым ssh.service и отключённым ssh.socket, вопреки распространённому мнению, будто Debian 13 при чистой установке переходит на сокет. При активации через сокет на порту 22 слушает сам systemd и запускает свежий sshd только при входящем соединении. У этого есть приятный побочный эффект: изменения в sshd_config и так вступают в силу при следующем подключении, потому что каждый процесс соединения перечитывает конфигурацию заново. Перечитывание конфигурации нужно только для настроек, которые касаются самого слушателя, то есть Port и ListenAddress.
Поэтому не гадайте, а спросите систему, какой юнит несёт службу именно у вас:
systemctl is-enabled ssh.socket ssh.service
| Система | ssh.socket | ssh.service | Особенность |
| Ubuntu 24.04 LTS | enabled | disabled | активация через сокет по умолчанию, ListenStream на 0.0.0.0:22 и [::]:22 |
| Debian 13 (trixie) | disabled | enabled | Accept=no |
| Debian 12 (bookworm) | disabled | enabled | Accept=no |
| Ubuntu 22.04 LTS (а также Debian 11) | disabled | enabled | Accept=yes, то есть отдельный процесс на каждое соединение через ssh@.service |
И наконец команда, которая верна на всех четырёх системах:
sudo sshd -t && sudo systemctl try-restart ssh.socket ssh.service
try-restart перезапускает юнит только тогда, когда тот действительно активен, а второй оставляет нетронутым. Гадать не приходится. Обе части требуют root: sshd лежит в /usr/sbin, а в Debian этот каталог не входит в PATH обычного пользователя, да и try-restart в любом случае обращается к systemd. Без sudo вызов завершается, в зависимости от системы, сообщением sshd: command not found или sshd: no hostkeys available, потому что ключи хоста в /etc/ssh доступны для чтения только root.
Столь же сознательно не применяется и отдельный systemctl restart ssh.socket, хотя эта команда и встречается во многих руководствах. На Debian 13 и Debian 12 она обрывается сообщением Job failed. See journalctl -xe for details., а в журнале к этому добавляется ssh.socket: Socket service ssh.service already active, refusing. Новая конфигурация при этом не становится активной, контрольная проверка по-прежнему выдаёт Permission denied (publickey,password). На Ubuntu 22.04 и Debian 11 команда проходит без сообщения об ошибке, но останавливает ssh.service и переводит хост на активацию через сокет. Поскольку ssh.socket там остаётся отключённым, при следующей перезагрузке снова поднимается ssh.service, то есть режим работы незаметно переключается туда и обратно. try-restart сразу для обоих юнитов избавляет и от того, и от другого.
Сознательно не применяется и reload: на системе, где порт держит ssh.socket, команда systemctl reload ssh отвечает так:
fatal: Cannot bind any address.
После этого служба находится в состоянии ошибки и уже отпустила свой порт. У перезапуска такой проблемы нет, и существующие сеансы он точно так же не разрывает.
Если вы хотите перенести порт, начинается следующее различие между дистрибутивами. Ubuntu 24.04 создаёт конфигурацию сокета через генератор systemd из sshd_config, там после смены порта достаточно:
sudo systemctl daemon-reload
sudo systemctl try-restart ssh.socket ssh.service
Если же в Debian вы сознательно перешли на ssh.socket, порт задаётся в самом юните, и правка в sshd_config остаётся без эффекта. Он прописывается файлом-дополнением через sudo systemctl edit ssh.socket: пустая строка ListenStream= для сброса и вторая строка с новым значением. Результат потом проверяйте по реальности, а не по конфигурации:
sudo ss -tlnp
Доказательство: sshd -T и контрольная проверка
Команда, отработавшая без ошибок, ещё не доказательство. Доказательство — это итоговая развёрнутая конфигурация, в которой уже учтены все подключённые файлы:
sudo sshd -T | grep -E '^(passwordauthentication|kbdinteractiveauthentication|pubkeyauthentication|permitrootlogin|usepam|port|authorizedkeysfile)'
Ожидается вывод примерно такого вида:
port 22
permitrootlogin prohibit-password
pubkeyauthentication yes
passwordauthentication no
kbdinteractiveauthentication no
usepam yes
authorizedkeysfile .ssh/authorized_keys .ssh/authorized_keys2
Если там несмотря на ваш файл значится passwordauthentication yes, то в каталоге лежит файл, который по алфавиту стоит раньше вашего. Возвращайтесь к разделу о порядке чтения.
Для проверки одного параметра хватит той же команды с более узким фильтром:
sudo sshd -T | grep -i passwordauthentication
За sudo впереди говорят две причины. Во-первых, sshd -T читает ключи хоста, а они в /etc/ssh доступны для чтения только root. Во-вторых, сам sshd лежит в /usr/sbin, и в Debian этот каталог не входит в PATH обычного пользователя, поэтому вызов без sudo там заканчивается сообщением sshd: command not found. Кто хочет обойтись без sudo, пишет полный путь /usr/sbin/sshd.
Начиная с OpenSSH 9.3, то есть на Ubuntu 24.04 (9.6p1) и Debian 13 (10.0p2), дополнительно есть sshd -G. Эта опция разбирает ту же конфигурацию, но не требует ни читаемых ключей хоста, ни существующего /run/sshd, что делает её пригодной для проверок в автоматизации и в контейнерах. На Debian 12 (9.2p1), Ubuntu 22.04 (8.9p1) и Debian 11 (8.4p1) этой опции ещё нет, там sshd отвергает её как неизвестную. Поэтому для руководства, которое действует везде, правильным выбором остаётся sudo sshd -T.
Теперь контрольная проверка, причём из нового терминала, пока старый сеанс остаётся открытым. Принудительно затребуйте вход по паролю и отключите ключи для этой попытки:
ssh -o PubkeyAuthentication=no -o PreferredAuthentications=password,keyboard-interactive root@203.0.113.10
Правильно, если вас отвергают сразу и без единого запроса пароля:
root@203.0.113.10: Permission denied (publickey).
Решает содержимое скобок. Если там стоит (publickey,password) или появляется запрос пароля, вход по паролю всё ещё открыт. Только когда контрольная проверка чисто проваливается, а обычное соединение по ключу по-прежнему проходит, закрывайте старый сеанс и останавливайте таймер отката:
sudo systemctl stop ssh-rollback.timer
Сообщения об ошибках дословно
Permission denied (publickey). Сервер принимает только ключи, а ваш не подходит. Запустите ssh -v и посмотрите, какой файл вообще был предложен. Самые частые причины: неверное имя пользователя, ключ в authorized_keys не того пользователя, либо вы закрыли root, но по-прежнему входите как root.
Authentication refused: bad ownership or modes for directory /home/hani/.ssh Эта строка появляется не у вас на экране, а в журнале сервера, увидеть её можно через sudo journalctl -u ssh -n 50 --no-pager. Не фильтруйте при этом по -t sshd: начиная с OpenSSH 9.8, то есть в Debian 13 прямо из коробки, сеансы работают в отдельном процессе sshd-session, и под тегом sshd остаются только сообщения слушателя, но ни одной попытки входа. Кто хочет фильтровать по тегам, берёт sudo journalctl -t sshd -t sshd-session -n 50 --no-pager. OpenSSH отклоняет ключи, если домашний каталог, .ssh или authorized_keys доступны на запись группе или всем остальным. Исправление:
chmod go-w ~ && chmod 700 ~/.ssh && chmod 600 ~/.ssh/authorized_keys
WARNING: UNPROTECTED PRIVATE KEY FILE! или же Load key "/home/hani/.ssh/id_ed25519": bad permissions. Та же проблема на стороне клиента. Решает её chmod 600 ~/.ssh/id_ed25519.
Too many authentication failures Ваш агент по очереди предлагает все загруженные ключи и превышает при этом MaxAuthTries. Ограничьте соединение одним-единственным ключом: ssh -o IdentitiesOnly=yes -i ~/.ssh/id_ed25519 hani@203.0.113.10.
sign_and_send_pubkey: no mutual signature supported Старый RSA-ключ с подписью SHA-1 попадает на сервер, который её больше не принимает. Создайте ключ ed25519, вместо того чтобы крутить PubkeyAcceptedAlgorithms.
Bad owner or permissions on C:\Users\hani\.ssh\config Клиент под Windows проверяет права доступа к своему конфигурационному файлу. В свойствах файла на вкладке безопасности отключите наследование и удалите все записи, кроме вашей учётной записи и SYSTEM.
WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! Ключ хоста у сервера отличается от прошлого раза. После переустановки это ожидаемо, в остальных случаях нет. Удаляйте старую запись прицельно командой ssh-keygen -R 203.0.113.10, и только если вы знаете причину.
Доступ потерян: путь через консоль в личном кабинете
Если оба сеанса потеряны и ни одно соединение больше не проходит, это не потеря данных, а обходной путь. У каждого KVM root-сервера и каждого выделенного сервера в KernelHost есть в личном кабинете консоль, которая подключена к экранному выводу системы и не зависит от сетевого стека сервера. То есть вы попадёте внутрь даже тогда, когда SSH уже вообще не слушает.
- Войдите в личный кабинет, откройте нужный сервер и запустите консоль.
- В приглашении на вход авторизуйтесь как root с паролем, который был выдан при развёртывании. Вход через консоль идёт не по SSH, и
PermitRootLoginего не затрагивает. - Откатите изменение:
sudo rm /etc/ssh/sshd_config.d/01-hardening.conf - Проверьте и перезапустите:
sudo sshd -t && sudo systemctl try-restart ssh.socket ssh.service - Если SSH не работает совсем, помогут
sudo systemctl status ssh.socket ssh.serviceи взгляд вsudo journalctl -u ssh -n 50 --no-pager. Код возврата 3 уstatusозначает лишь, что один из двух юнитов неактивен; в Debian с отключённымssh.socketэто нормальная ситуация.
Две меры предосторожности почти всегда избавят вас от этого пути. Запишите пароль root, прежде чем отключать вход по паролю, потому что консоли он понадобится. И проверьте активный файрвол, прежде чем переносить порт SSH. Смена порта без соответствующего разрешающего правила отрезает доступ так же надёжно, как сломанный sshd_config, но выглядит иначе: вместо Permission denied вы получите Connection timed out.
Кратко
- Держите второй сеанс открытым, пока третье, новое соединение не заработает доказуемо.
- ed25519 с
-a 100и парольной фразой, открытый ключ черезssh-copy-id, в Windows через конвейер. - Проверьте вход по ключу, прежде чем отключать вход по паролю.
- Правила защиты пишите в
/etc/ssh/sshd_config.d/01-hardening.conf, а не вsshd_config. Побеждает первое найденное значение, отсюда и низкий номер. - Не забудьте
KbdInteractiveAuthentication no, иначе обходной путь через PAM останется открытым. - Активируйте через
sudo sshd -t && sudo systemctl try-restart ssh.socket ssh.service, ни черезreload, ни через отдельныйrestart ssh.socket. - Заранее выясните командой
systemctl is-enabled ssh.socket ssh.service, какой юнит вообще активен. Ubuntu 24.04 использует сокет, Debian 13 и Debian 12 используют службу. - Доказательство даёт
sudo sshd -Tвместе с принудительной попыткой входа по паролю из нового сеанса. - Аварийный путь: консоль в личном кабинете, для неё держите наготове пароль root.
Следующие очевидные слои защиты описаны в наших статьях о fail2ban и о файрволе UFW. Но важнее их обоих то, что вы только что сделали.
Частые вопросы
Почему вход по паролю остаётся активным несмотря на PasswordAuthentication no?
Нужно ли перезапускать службу после изменения sshd_config?
Почему стоит избегать reload?
Как перенести ключ на сервер из Windows, если ssh-copy-id там нет?
Как доказать, что вход по паролю действительно закрыт?
Что делать, если я закрыл доступ самому себе?
Что лучше выбрать: PermitRootLogin no или prohibit-password?
2026 KernelHost GmbH. Все права защищены. Эта инструкция охраняется авторским правом. Публикация на других сайтах, в том числе частично или в изменённом виде, без нашего письменного согласия не разрешается. Цитирование с указанием источника и активной ссылкой мы приветствуем.

