Обзор
SSH — самая часто используемая технология из всех, что описаны в этой базе, и обычно самая недоиспользуемая. Типичная практика сводится к ssh user@host, копированию ключа через ssh-copy-id и правке пары строк в sshd_config; всё остальное — мультиплексирование соединений, прыжки через бастион, сертификаты вместо ключей, ограниченные ключи, туннели — остаётся за кадром, хотя решает ровно те задачи, на которые ежедневно тратится время.
Статья идёт со стороны клиента к серверу: сначала ~/.ssh/config, из которого выводится большая часть удобства, затем ключи и агент, доступ через промежуточные хосты, сертификаты как замена раздаче ключей, туннели и, наконец, серверная конфигурация и отладка.
Чего здесь нет намеренно: базового списка настроек sshd_config для защиты сервера — PermitRootLogin, PasswordAuthentication, тайм-ауты, защита от перебора. Он уже разобран в заметках про hardening Linux и hardening FreeBSD; дублировать его смысла нет, и серверный раздел ниже посвящён механике конфигурации, а не перечню параметров.
Версия-якорь — OpenSSH 10.5 (август 2026). Версии указаны там, где возможность появилась недавно: в дистрибутивах живут разные сборки, и часть приёмов ниже на OpenSSH из старого LTS не заработает.
~/.ssh/config
Первое значение выигрывает
Одно правило объясняет большую часть недоразумений с конфигом, и его стоит запомнить дословно: используется первое найденное значение каждого параметра. Документация формулирует это прямо и делает из этого вывод — специфичные для хоста объявления должны стоять ближе к началу файла, а общие умолчания в конце.
Интуиция здесь работает наоборот. От конфигурационного файла ожидаешь поведения «последнее переопределяет предыдущее», как в большинстве форматов, и раскладываешь блоки в обратном порядке. Результат — параметр из общего блока Host *, стоящего сверху, молча выигрывает у специфичного блока ниже, и правки «не применяются».
# ПРАВИЛЬНО: от частного к общему
Host prod-db
HostName 10.0.4.12
User postgres
Port 2222
Host *.internal
User admin
IdentityFile ~/.ssh/id_ed25519_work
Host *
ServerAliveInterval 30
AddKeysToAgent yes
Наследования в объектном смысле нет: блоки не вкладываются, а накапливаются. Хост prod-db получает User postgres из своего блока и ServerAliveInterval 30 из Host *, потому что ServerAliveInterval больше нигде не встретился раньше.
Host сравнивает с шаблоном имя, которое вы набрали в командной строке, — не с реальным именем и не с адресом. Match мощнее: он умеет условия по итоговому имени (Match host), по пользователю, по результату выполнения команды (Match exec) и их комбинации. Типовое применение — разная конфигурация в зависимости от того, где вы сейчас находитесь:
Match host *.corp.example.com exec "nc -z -w1 10.0.0.1 22 2>/dev/null"
ProxyJump none # внутри офиса бастион не нужен
Match host *.corp.example.com
ProxyJump bastion.example.com
IdentityFile и IdentitiesOnly
Ошибка Too many authentication failures — самая частая и самая непонятная из сообщений SSH, и причина у неё не та, о которой думают. Сервер не отвергает ваш ключ: он отвергает попытку номер шесть, потому что MaxAuthTries по умолчанию равен шести, а клиент до этого успел предложить пять чужих ключей.
Механика такая. Клиент перебирает все ключи, какие знает: из агента, из ~/.ssh/config и стандартные имена в ~/.ssh/. Каждое предложение — отдельная попытка аутентификации с точки зрения сервера. С десятком ключей в агенте до нужного дело просто не доходит.
Лечится параметром, название которого не намекает на проблему:
Host prod-db
IdentityFile ~/.ssh/id_ed25519_prod
IdentitiesOnly yes # предлагать ТОЛЬКО указанный ключ
IdentitiesOnly yes заставляет использовать только явно заданные файлы, игнорируя всё, что предлагает агент. На машине, где ключей больше двух, это стоит поставить в Host * и задавать IdentityFile каждому хосту явно.
ControlMaster: почему это меняет ощущения от работы
Каждое новое соединение SSH — это TCP-рукопожатие, обмен ключами, аутентификация. На локальной сети это десятки миллисекунд, через океан и бастион — секунды. Работа, состоящая из десятка коротких команд подряд, превращается в ожидание.
Мультиплексирование решает это радикально: первое соединение становится «мастером», последующие идут по нему дополнительными каналами и не требуют ни рукопожатия, ни аутентификации.
Host *
ControlMaster auto
ControlPath ~/.ssh/cm/%C
ControlPersist 10m
Каталог нужно создать заранее (mkdir -p -m 700 ~/.ssh/cm) — сам SSH его не создаст. %C — хеш от локального хоста, целевого хоста, порта, пользователя и цепочки прыжков; документация рекомендует включать в ControlPath либо %h, %p и %r, либо %C вместо них. Хеш здесь практичнее: путь к unix-сокету ограничен примерно сотней символов, и на длинных именах хостов развёрнутый вариант в этот лимит не помещается.
ControlPersist 10m держит мастер-соединение живым десять минут после закрытия последней сессии — следующая команда к тому же хосту подключится мгновенно.
Эффект заметнее всего там, где соединений много и они короткие: git по SSH, rsync в цикле, Ansible (у него есть собственная настройка мультиплексирования по той же причине), любые скрипты с несколькими ssh подряд.
Грабли ровно одни — залипший сокет. Если сеть оборвалась, мастер может остаться в состоянии, когда он не работает и не умирает, и все новые подключения к этому хосту виснут. Управление явное:
ssh -O check prod-db # жив ли мастер
ssh -O exit prod-db # закрыть мастер
ssh -S none prod-db # подключиться в обход мультиплексирования
Ещё одно следствие, о котором стоит знать заранее: пока мастер жив, изменения в ~/.ssh/config для этого хоста не применяются — новые сессии наследуют параметры мастера.
Ключи
Что генерировать
ssh-keygen -t ed25519 -C "user@laptop-2026"
Ed25519 — разумное умолчание: ключи фиксированной короткой длины, быстрая проверка подписи, отсутствие вариантов «сделать неправильно». RSA остаётся нужен только для совместимости со старым оборудованием; если он всё же берётся, то не короче 4096 бит. DSA обсуждать не нужно вовсе: поддержка отключена по умолчанию с 2015 года, а в OpenSSH 10.0 (апрель 2025) удалена совсем.
Аппаратные токены поддерживаются нативно, без стороннего софта:
ssh-keygen -t ed25519-sk -O resident -O verify-required -C "yubikey-2026"
Приватная часть такого ключа не покидает токен: в файле лежит только «указатель», и без физического устройства он бесполезен. verify-required добавляет требование подтвердить личность PIN-кодом или биометрией, resident записывает ключ в память токена, откуда его можно выгрузить на новой машине (ssh-keygen -K). Это лучшая на сегодня защита от кражи ключа с ноутбука.
Отдельно — постквантовые подписи. В OpenSSH 10.4 (июль 2026) появился гибридный тип mldsa44-ed25519, сочетающий ML-DSA-44 с Ed25519. Он экспериментальный и по умолчанию выключен: чтобы им пользоваться, тип нужно явно добавить в PubkeyAcceptedAlgorithms и HostKeyAlgorithms с обеих сторон. Для обмена ключами постквантовая схема, наоборот, уже стала умолчанием — mlkem768x25519-sha256 включён по умолчанию с OpenSSH 10.0, так что защита самого канала от «сохранить сейчас, расшифровать потом» работает без настройки. С 10.1 клиент вдобавок предупреждает, когда согласован не постквантовый обмен ключами — то есть когда сервер слишком старый; предупреждение управляется параметром WarnWeakCrypto, который по умолчанию включён, а конкретно это сообщение гасится флагом no-pq-kex.
Парольная фраза и агент
Ключ без парольной фразы — это файл, дающий доступ ко всей инфраструктуре тому, кто получил ваш диск. Фраза ставится всегда, а чтобы не вводить её постоянно, ключ загружается в агент:
ssh-add -t 8h ~/.ssh/id_ed25519 # забыть ключ через 8 часов
ssh-add -l # что сейчас в агенте
ssh-add -D # выгрузить всё
Ограничение времени жизни — не формальность: агент хранит расшифрованный ключ в памяти процесса, и бессрочно загруженный ключ означает, что компрометация сессии в любой момент рабочего дня равна компрометации ключа. Рабочий день как срок жизни — разумный компромисс.
AddKeysToAgent yes в конфиге добавляет ключ в агент при первом использовании, так что явный ssh-add нужен редко.
Доступ через бастион
ProxyJump
Бастион (jump host) — единственная машина, доступная снаружи, через которую идёт доступ к остальным. Раньше это делалось через ProxyCommand с вложенным nc; с появлением ProxyJump конструкция свелась к одному параметру:
ssh -J bastion.example.com prod-db
ssh -J user1@bastion:2222,user2@relay prod-db # цепочка из двух прыжков
В конфиге:
Host bastion
HostName bastion.example.com
User jump
IdentityFile ~/.ssh/id_ed25519_bastion
Host prod-*
ProxyJump bastion
User deploy
IdentityFile ~/.ssh/id_ed25519_prod
Принципиальное свойство: соединение с целевым хостом шифруется от вашей машины до цели, а бастион только пересылает байты. Он не видит содержимое сессии и не участвует в аутентификации на цели. Ключ для цели никогда не покидает вашу машину.
Прыжки нужны не только ssh. scp и sftp понимают -J, а rsync — через параметр удалённой оболочки:
rsync -avP -e 'ssh -J bastion' ./data/ prod-db:/srv/data/
Если хост описан в ~/.ssh/config, отдельного флага не нужно вовсе — rsync -avP ./data/ prod-db:/srv/data/ подхватит ProxyJump из конфига.
ProxyCommand остаётся нужен там, где транспорт не сводится к «подключиться по SSH к промежуточному хосту»: доступ через HTTP CONNECT-прокси, через cloudflared, через нестандартную утилиту. Для обычного бастиона он проигрывает ProxyJump во всём.
Agent forwarding и почему его лучше не включать
ForwardAgent yes (флаг -A) пробрасывает сокет вашего агента на удалённый хост, чтобы оттуда подключаться дальше вашими ключами. Соблазн очевиден, опасность — нет.
Проброшенный сокет — это файл на удалённой машине. Любой, кто может обойти права доступа к нему — а root может всегда, — получает возможность обращаться к вашему агенту, пока сессия открыта. Ключ он не украдёт: агент не отдаёт приватную часть. Но он может попросить агент подписать что угодно, а этого достаточно, чтобы зайти под вами на любой хост, куда пускает этот ключ. Документация предупреждает об этом прямо, и по умолчанию ForwardAgent выключен. Одну грань риска в OpenSSH 10.1 подрезали: сокеты агента — и локальные, и проброшенные на сервер — переехали из /tmp в ~/.ssh/agent/, так что процесс, которому виден только /tmp, до агента больше не дотягивается. От того, у кого на удалённой машине root, это не спасает.
Практический вывод простой: на бастионе, которым пользуется не только администратор, проброс агента означает доверие всем, у кого там root. Правильная замена — ProxyJump: он решает ту же задачу и никакого агента никуда не пробрасывает. Если проброс всё же необходим, ограничивайте его конкретным хостом (Host bastion + ForwardAgent yes, но не в Host *) и используйте агент с подтверждением каждой подписи — ssh-add -c, который заставляет подтверждать каждое использование ключа локально.
Сертификаты вместо authorized_keys
Что это решает
Схема с authorized_keys имеет два дефекта, которые проявляются с ростом парка машин.
Первый — отзыв доступа. Уволился сотрудник, и его ключ надо убрать со всех серверов. Пока серверов пять, это скрипт; когда их пятьдесят и часть выключена, гарантии, что ключ убран отовсюду, нет.
Второй — known_hosts на стороне клиента. Каждый новый сервер вызывает вопрос про отпечаток, на который все отвечают «yes» не глядя. Переустановка сервера ломает запись, и пользователь видит грозное предупреждение о возможной атаке, которое приучается игнорировать — вместе с настоящими предупреждениями.
Сертификаты решают обе задачи одним механизмом: стороны доверяют не множеству ключей, а одному ключу удостоверяющего центра, который эти ключи подписывает. Формат OpenSSH — собственный, заметно более простой, чем X.509.
Свой CA
ssh-keygen -t ed25519 -f ssh_user_ca -C "user CA"
ssh-keygen -t ed25519 -f ssh_host_ca -C "host CA"
Два отдельных CA — не перестраховка, а разделение ролей: ключ, подписывающий пользователей, не должен уметь выдавать себя за сервер, и наоборот.
Пользовательский сертификат:
ssh-keygen -s ssh_user_ca -I "alice@2026-08" -n alice,deploy \
-V +8h ~/.ssh/id_ed25519.pub
-I — идентификатор, попадающий в логи сервера и служащий единицей отзыва. -n — список принципалов, то есть учётных записей, под которыми этот сертификат разрешает входить. -V +8h — срок действия; результат кладётся рядом с ключом как id_ed25519-cert.pub и подхватывается автоматически.
Короткий срок действия — главная ценность схемы. Сертификат на восемь часов не нужно отзывать: он протухнет сам. Отзыв доступа сводится к тому, чтобы перестать выдавать новые.
На сервере доверие настраивается двумя строками:
# /etc/ssh/sshd_config.d/60-ca.conf
TrustedUserCAKeys /etc/ssh/ssh_user_ca.pub
RevokedKeys /etc/ssh/revoked_keys.krl
Важная деталь: сертификат без списка принципалов через TrustedUserCAKeys к аутентификации не допускается. Если нужно отвязать имена принципалов от имён учётных записей — например, пускать всех с принципалом sre под пользователя deploy, — это делается через AuthorizedPrincipalsFile.
Хостовые сертификаты
Вторая половина схемы, которую часто пропускают, а зря — именно она убирает вопросы про отпечатки.
ssh-keygen -s ssh_host_ca -I "web01" -h -n web01.example.com,10.0.1.5 \
-V +52w /etc/ssh/ssh_host_ed25519_key.pub
# /etc/ssh/sshd_config.d/60-ca.conf
HostCertificate /etc/ssh/ssh_host_ed25519_key-cert.pub
На клиенте вместо десятков строк в known_hosts — одна:
@cert-authority *.example.com ssh-ed25519 AAAAC3NzaC1lZDI1... host CA
Теперь любой сервер домена, предъявивший сертификат этого CA, принимается без вопросов, а переустановленный сервер с новым ключом и новым сертификатом не вызывает предупреждений. При этом настоящая подмена сервера по-прежнему обнаруживается: у злоумышленника нет подписи CA.
Отзыв
Для случаев, когда ждать истечения срока нельзя, есть списки отзыва:
ssh-keygen -k -f revoked_keys.krl compromised_key.pub # создать KRL
ssh-keygen -k -f revoked_keys.krl -u another_key.pub # дополнить
ssh-keygen -Q -f revoked_keys.krl some_key.pub # проверить
Файл, указанный в RevokedKeys, читается при каждой попытке аутентификации, поэтому заменять его надо атомарно (создать рядом и mv), а не править на месте. И отдельная мина: если файл станет нечитаемым, аутентификация по ключам будет отвергнута для всех.
Когда это окупается
Честный ответ: не сразу. Инфраструктура CA — это защищённое хранение ключа удостоверяющего центра, процедура выдачи сертификатов и её автоматизация. Ради трёх личных серверов городить это бессмысленно — authorized_keys и один-два ключа проще во всём.
Ориентиры, при которых схема начинает выигрывать: машин больше двух-трёх десятков, людей с доступом больше пяти, серверы регулярно пересоздаются, или есть требование отзывать доступ гарантированно и быстро. Отдельный сильный аргумент — эфемерная инфраструктура: в автомасштабируемой группе новые хосты появляются постоянно, и раздавать на них ключи бессмысленно, а хостовой сертификат выдаётся при подготовке образа.
Промежуточный вариант, который часто оказывается достаточным: только хостовые сертификаты, без пользовательских. Это дёшево — один CA и подпись при развёртывании, — и снимает всю боль с known_hosts, оставив раздачу пользовательских ключей как была.
known_hosts
Модель доверия здесь называется TOFU (trust on first use): первое подключение принимается на веру, дальше ключ запоминается и сверяется. Слабое место очевидно — если подменить сервер удалось при первом подключении, схема не сработает вовсе.
Три вещи, которые стоит знать про этот файл.
Хеширование имён. HashKnownHosts заменяет имена и адреса хешами, чтобы файл не выдавал список ваших серверов тому, кто получил доступ к домашнему каталогу. Здесь заметное расхождение между вендорами: upstream-умолчание — no, а Debian и Ubuntu включают его в /etc/ssh/ssh_config. Отсюда разное поведение на разных машинах и невозможность вручную найти строку grep-ом там, где хеширование включено. Преобразовать существующий файл можно через ssh-keygen -H, а найти запись в хешированном — через ssh-keygen -F имя.
ssh-keygen -F prod-db # найти запись (работает и с хешами)
ssh-keygen -R prod-db # удалить запись после переустановки
SSHFP в DNS. Отпечаток ключа можно опубликовать в DNS-записи и проверять по ней вместо TOFU:
ssh-keygen -r web01.example.com # сгенерировать записи для зоны
VerifyHostKeyDNS yes
При yes клиент молча доверяет отпечатку, полученному из DNS безопасно, то есть при работающем DNSSEC; без него запись считается ненадёжной, и поведение откатывается к вопросу пользователю. Отсюда честная оценка: без DNSSEC схема не даёт настоящей гарантии, и в этом случае хостовые сертификаты — более практичный путь.
UpdateHostKeys. Современный клиент умеет принимать от сервера уведомления о дополнительных ключах и сам обновлять known_hosts. Это то, что позволяет серверу безболезненно перейти, например, с RSA на Ed25519: клиент узнает новый ключ заранее, по уже доверенному соединению.
Туннели
Три флага, которые стоит различать чётко.
-L — локальный проброс. Порт на вашей машине ведёт к сервису, доступному с удалённого хоста. Классика — доступ к базе, слушающей только на localhost сервера:
ssh -L 5432:localhost:5432 -N prod-db
psql -h 127.0.0.1 -p 5432
Адрес localhost здесь разрешается на удалённой стороне, и это ровно то, что нужно. Через бастион к третьей машине: ssh -J bastion -L 5432:10.0.4.12:5432 -N gateway.
-R — обратный проброс. Порт на удалённом хосте ведёт к сервису на вашей машине. Основной инструмент для доступа к машине без белого адреса:
ssh -R 8080:localhost:80 -N vps
По умолчанию проброшенный порт слушает только на loopback удалённой машины. Чтобы он был доступен извне, на сервере нужен GatewayPorts yes или clientspecified — и это решение с последствиями: открытый таким образом порт доступен всему интернету, без какой-либо аутентификации со стороны SSH. clientspecified безопаснее, потому что оставляет выбор адреса привязки клиенту (-R 127.0.0.1:8080:...).
Сценарий публикации домашнего сервиса через арендованный VPS разобран отдельно — там же про то, почему для постоянной схемы обратный туннель SSH проигрывает WireGuard: см. самохостинг за NAT.
-D — SOCKS-прокси. Динамический проброс: SSH поднимает локальный SOCKS5-прокси, и приложения, умеющие в него ходить, получают выход в сеть с удалённой машины.
ssh -D 1080 -N bastion
curl --socks5-hostname 127.0.0.1:1080 http://internal-app.corp/
Форма --socks5-hostname существенна: она заставляет разрешать имена на удалённой стороне. С обычным --socks5 имя резолвится локально, внутренние имена не разрешаются, а ваши DNS-запросы утекают наружу.
Флаг -N во всех примерах означает «не выполнять команду», а -f уводит процесс в фон. Для долгоживущих туннелей лучше не -f, а юнит systemd с Restart=always: голый ssh умирает вместе с сетью и сам не возвращается.
Серверная сторона
sshd_config.d и порядок включения
Начиная с OpenSSH 8.2 (февраль 2020) sshd_config поддерживает директиву Include, и это меняет подход к правкам. Дистрибутивы семейства Debian ставят её в начало основного файла:
Include /etc/ssh/sshd_config.d/*.conf
Смысл — не в эстетике. Правки, положенные отдельным файлом, переживают обновление пакета: менеджер пакетов не трогает ваш файл в sshd_config.d/ и не задаёт вопросов о конфликте с новой версией основного конфига. При правке основного файла вы получаете либо диалог «оставить своё или взять новое», либо, что хуже, тихую потерю настроек.
Здесь же ловушка, вытекающая из правила первого значения: Include обязан стоять в начале файла. Если поставить его в конец, все параметры уже будут заданы основным конфигом, и файлы из sshd_config.d/ не переопределят ничего. Файлы по глобу читаются в лексическом порядке, поэтому их принято нумеровать: 10-, 50-, 99-.
# /etc/ssh/sshd_config.d/50-hardening.conf
PermitRootLogin no
PasswordAuthentication no
Match: исключение из правила
Match в sshd_config работает не так, как остальные директивы, и это стоит держать в голове. Параметры внутри сработавшего блока переопределяют глобальные, а не проигрывают им по правилу первого значения. При этом, если один и тот же параметр встречается в нескольких сработавших блоках Match, применяется первый.
Всё, что идёт после строки Match, относится к этому блоку до следующего Match или до конца файла. Отступы ни на что не влияют — это частая ошибка при чтении чужого конфига: визуально «глобальный» параметр в конце файла на самом деле принадлежит последнему блоку.
# глобально — строго
PasswordAuthentication no
AllowTcpForwarding no
X11Forwarding no
Match Group developers
AllowTcpForwarding yes # разработчикам нужны туннели
Match User backup, Address 10.0.0.0/8
ForceCommand /usr/local/bin/backup-only
PermitTTY no
ForceCommand выполняет заданную команду вместо всего, что запросил клиент, — оригинальный запрос при этом доступен в переменной SSH_ORIGINAL_COMMAND. Это основа для ограниченных учётных записей: резервное копирование, приём git-репозиториев, единственная разрешённая операция.
Ограниченные ключи
Ограничения можно навесить и на конкретный ключ, без правки серверного конфига — префиксом перед ключом в authorized_keys:
restrict,from="10.0.0.0/8",command="/usr/local/bin/deploy.sh" ssh-ed25519 AAAAC3... deploy-bot
restrict — правильная отправная точка. Он отключает всё сразу: проброс портов, агента, X11, выделение терминала и выполнение ~/.ssh/rc. Ключевое его свойство описано в документации явно: новые виды ограничений, которые появятся в будущих версиях, автоматически попадают в этот набор. Старый способ — перечислять no-port-forwarding,no-agent-forwarding,no-X11-forwarding,no-pty — оставляет дыру ровно в момент, когда в OpenSSH добавится новая возможность, о которой ваш конфиг не знает.
Нужное включается обратно поимённо: restrict,pty даёт терминал, оставив остальное закрытым. from= привязывает ключ к адресам, command= — к единственной команде.
Как менять конфигурацию, не теряя доступ
Перезапуск sshd с ошибкой в конфиге на удалённой машине без консоли в панели провайдера — это конец рабочего дня. Дисциплина проста и стоит тридцати секунд:
sshd -t # проверить синтаксис
sshd -T # вывести итоговую конфигурацию целиком
sshd -T -C user=deploy,host=web01,addr=10.0.1.5 # с учётом Match-блоков
sshd -T особенно полезен при разборе, почему настройка «не применилась»: он показывает не то, что написано в файлах, а то, что получилось после всех включений и правила первого значения.
Вторая половина дисциплины — не закрывать текущую сессию, пока новая не проверена:
systemctl reload sshd # reload, а не restart: существующие сессии живут
# в ДРУГОМ терминале, не закрывая текущий:
ssh -v user@host # проверить, что вход работает
reload предпочтительнее restart именно потому, что не рвёт установленные соединения. Если новый конфиг сломал вход, у вас остаётся открытая сессия, чтобы откатить правку.
Отладка
Первое средство — подробный вывод клиента. Уровни различаются: -v показывает ход аутентификации, -vv добавляет обмен параметрами, -vvv — пакеты.
ssh -v prod-db 2>&1 | grep -i "offering\|authentications that can continue\|Server accepts"
Что искать в выводе: строку Authentications that can continue (какие методы сервер вообще принимает), последовательность Offering public key (какие ключи предлагает клиент и в каком порядке), Server accepts key (сервер согласился на ключ, дальше идёт подпись).
Начиная с OpenSSH 10.5 для того же вопроса есть отдельный режим: ssh -Z user@host печатает ключи, которые будут предложены, в порядке использования, и завершается, не подключаясь к серверу. При разборе Too many authentication failures это быстрее, чем читать -v.
| Симптом | Обычная причина | Куда смотреть |
|---|---|---|
Too many authentication failures |
клиент перебрал чужие ключи до нужного | IdentitiesOnly yes, ssh-add -D |
Permission denied (publickey) |
ключа нет в authorized_keys либо не тот пользователь |
-v: предлагался ли нужный ключ; логи sshd |
| Ключ верный, но вход не проходит | права на ~/.ssh или authorized_keys |
chmod 700 ~/.ssh, 600 authorized_keys; владелец каталога |
| Пароль спрашивается вместо ключа | PubkeyAuthentication no или ключ не предложен |
sshd -T | grep -i pubkey |
| Правка конфига «не работает» | сработало первое значение выше по файлу | ssh -G host, sshd -T |
| Зависает после установления TCP | залипший мастер ControlMaster |
ssh -O check, ssh -S none |
REMOTE HOST IDENTIFICATION HAS CHANGED |
сервер переустановлен либо подмена | ssh-keygen -R host после проверки отпечатка |
Симметричная команда для клиентской стороны — ssh -G host: она печатает итоговую конфигурацию для конкретного хоста после применения всех блоков, никуда не подключаясь. При разборе «почему берётся не тот ключ» это быстрее чтения конфига глазами.
Серверная сторона отлаживается через журнал (journalctl -u ssh -f) либо, если причина непонятна, запуском второго sshd на другом порту в отладочном режиме:
/usr/sbin/sshd -d -p 2222 # не трогая основной демон
ssh -p 2222 user@host
Сравнение с FreeBSD
OpenSSH входит в базовую систему FreeBSD, а не ставится пакетом, и отсюда несколько отличий.
Версия в базе отстаёт от upstream: и во FreeBSD 14, и во FreeBSD 15 это OpenSSH 10.0 при 10.5 в текущем релизе проекта. Разрыв обычно не критичен, но возможности последних версий там появляются с задержкой, и это стоит проверять перед тем, как закладываться на свежий параметр.
Директивы Include в поставляемом /etc/ssh/sshd_config нет — каталог sshd_config.d/ во FreeBSD не заведён по умолчанию, в отличие от Debian и производных. Сам механизм поддерживается, так что строку можно добавить самостоятельно, но из коробки правки идут в основной файл, и после freebsd-update его придётся сверять вручную.
Есть и специфичные для системы параметры. UseBlocklist yes включает интеграцию с blocklistd — штатным механизмом блокировки после неудачных попыток входа, который заменяет собой fail2ban; подробности в заметке про hardening FreeBSD. Имя сменилось в FreeBSD 15: в 14.x демон и параметр по-прежнему зовутся blacklistd и UseBlacklist, а sshd принимает оба написания как алиасы, так что конфиг переживает переход между ветками. VersionAddendum управляет строкой с меткой FreeBSD в баннере версии.
Если OpenSSH из базы не устраивает, в портах есть security/openssh-portable — свежая версия, устанавливаемая параллельно, со своим конфигом в /usr/local/etc/ssh/ и отдельным rc-скриптом. Держать оба демона одновременно на одном порту нельзя, и переключение требует аккуратности: отключить базовый sshd в rc.conf и включить портовый, не потеряв доступ.
Источники
- ssh_config(5) — правило первого значения,
Match,ControlMaster/ControlPathи токены,ProxyJump,IdentitiesOnly,HashKnownHosts,VerifyHostKeyDNS - sshd_config(5) —
Include, семантикаMatchкак переопределения,TrustedUserCAKeys,AuthorizedPrincipalsFile,RevokedKeys,HostCertificate - sshd(8), раздел AUTHORIZED_KEYS —
restrictи его свойство включать будущие ограничения - ssh-keygen(1), разделы CERTIFICATES и KEY REVOCATION LISTS — подписание ключей,
-I,-n,-V, генерация KRL - OpenSSH — release notes: 8.2 (
Includeв sshd_config), 9.8 (выделениеsshd-sessionизsshd), 10.0 (удаление DSA,mlkem768x25519-sha256по умолчанию, вынос аутентификации в отдельныйsshd-auth), 10.1 (WarnWeakCrypto, сокеты агента в~/.ssh/agent), 10.4 (гибридные подписиmldsa44-ed25519), 10.5 (ssh -Z) - Дерево исходников OpenSSH — текущая версия 10.5p1 по
version.h - Дерево исходников FreeBSD — версия OpenSSH в базе и поставляемый
sshd_configбезInclude - Debian: openssh-client, отличия умолчаний —
HashKnownHosts yesи другие параметры, заданные дистрибутивом
Связанные заметки
- Linux — безопасность: hardening, SELinux/AppArmor — базовый набор настроек
sshd_configи защита от перебора - FreeBSD — безопасность — то же со стороны FreeBSD,
blacklistdи PF overload tables - Самохостинг за NAT: VPS как точка входа — обратные туннели
-Rи почему для постоянной схемы берут WireGuard - Linux — сеть: стек, конфигурация, firewall — что происходит уровнем ниже туннелей
- Linux — пользователи, группы, PAM и systemd-logind — стек
/etc/pam.d/sshd, который вызывает sshd после проверки ключа - AEAD: аутентифицированное шифрование, nonce и лимиты — что происходит с трафиком после обмена ключами и почему Terrapin оказался провалом протокола, а не шифра
- ChaCha20 и Poly1305: устройство связки и почему она такая — как устроен
chacha20-poly1305@openssh.com, зачем ему два экземпляра шифра и что стандартизует черновик sshm - Обмен ключами: X25519, KEM, вывод ключей и привязка к транскрипту — что стоит за
mlkem768x25519-sha256иmldsa44-ed25519, exchange hash и вывод ключей по буквам - AES-GCM против ChaCha20-Poly1305: как выбирать — что означает умолчание
Ciphersв OpenSSH и когда клиенту стоит поднять AES-GCM явно
Из библиотеки института уносят только знания. Читателей здесь не считают: посещение не регистрируется, отметок в издании не остаётся, счётчиков нет ни у одного издания фонда.
Издание пополняется и пересверяется. Текст доступен на условиях CC BY-SA 4.0 — унесённое можно передавать дальше на тех же условиях.