Администратор подключается к серверу, вводит имя пользователя, а пароль система спрашивает секунд через тридцать. Иногда даже приглашение появляется с паузой, а иногда замирает уже после ввода пароля. Самое неприятное в этой истории то, что сеть живая, процессор отдыхает, диск не пилит, а сервер будто задумался о смысле жизни. На практике такая пауза почти всегда означает одно и то же: sshd ждёт ответа, который никогда не придёт, пока не сработает таймаут. Чаще всего виноваты обратный DNS-запрос (UseDNS) и попытка аутентификации через GSSAPI. Обе вещи включаются "по-умолчанию" во многих дистрибутивах и обе легко диагностируются, если знать куда смотреть. Ниже полная практика продакшен-диагностики: команды, реальные строки вывода, разбор причин и профилактика.
Откуда вообще берутся ровно тридцать секунд у DNS resolver
Число тридцать появляется не случайно. В glibc resolver (именно его вызывает sshd через getnameinfo для обратного запроса) по умолчанию заданы параметры: options timeout:5 и default attempts:2. То есть 5 секунд на попытку и 2 попытки на каждый сервер из /etc/resolv.conf. Если в файле два сервера имён и оба молчат, получаем: 5 секунд умножаем на 2 попытки и на 2 сервера - итого 20 секунд. Добавляем ещё одну попытку через поиск по dns на некоторых системах или третью строку nameserver - и вот уже 30 секунд ожидания. То есть пауза почти всегда кратна пяти и дробно связана с количеством nameserver-строк в resolv.conf.
# Содержимое resolver: смотрим, сколько серверов имён и какие опции
cat /etc/resolv.conf
Типичный вывод:
# Generated by NetworkManager
search example.internal
nameserver 10.7.0.2 # первый обратификатор за именами
nameserver 10.7.0.3 # запасной резолвер
options timeout:5 attempts:2 # параметры по умолчанию
Построчно: search задаёт суффикс для неполных имён, две записи nameserver - сами адреса DNS, а options отвечает за упорство resolver. Если оба сервера недоступны (сгоревший VLAN, закрытый UDP/53, нет маршрута) - resolver честно ждёт по таймауту на каждый и sshd в это время молчит снаружи.
Проверить resolver прямо из консоли можно так:
# Простой обратный запрос к IP клиента с замером времени
time getent hosts 192.168.1.50
Если команда выпадает в ошибку через 30 секунд (или ровно через 10, 20 - зависит от конфига) - диагноз ясен ещё до правки sshd. Заодно посмотрите живость самого резолвера: dig +time=1 +tries=1 @10.7.0.2 -x 192.168.1.50. Если и тут тишина - проблема не в SSH, а в сетевом пути до DNS.
Трассировка задержки на стороне клиента через ssh -vvv
Первое правило диагностики: не гадаем, а смотрим трассировку. На клиенте запускаем подключение с максимальным логированием - и видим, где именно замирает протокол.
# Подробная трассировка подключения, временные метки видны по задержкам строк
ssh -vvv -o ConnectTimeout=10 Адрес электронной почты защищен от спам-ботов. Для просмотра адреса в браузере должен быть включен Javascript. 'echo ok'
В выводе -vvv ищем место, где задержка возникает визуально (строки после паузы):
debug1: Connecting to db-01.example.internal [10.7.5.31] port 22.
debug1: Connection established.
debug1: Authenticating to db-01.example.internal:22 as 'admin'
debug3: send packet: type 20
debug1: SSH2_MSG_KEXINIT sent
...
debug1: Server host key: ssh-ed25519 SHA256:2n9X...
debug3: sign_and_send_pubkey: signing using rsa-sha2-512
debug2: we sent a publickey packet, wait for reply
# <-- вот здесь пауза в 30 секунд на глаз
debug1: Authentications that can continue: publickey,gssapi-keyex,gssapi-with-mic,password
Разбор: если зависание происходит мгновенно после "Authenticating to" и до появления списка методов - почти наверняка сервер ждёт обратного резолва IP клиента (UseDNS yes). Если же всё идёт быстро до "we sent a publickey packet" и оно замирает на попытке gssapi-with-mic или gssapi-keyex - виноват GSSAPI: клиент или сервер тянет Kerberos/KRB5 в пустоту.
Отдельно полезны опции -o PreferredAuthentications=password и -o GSSAPIAuthentication=no для быстрой проверки: если SSH стал работать мгновенно после отключения GSSAPI на клиенте - причина локализована.
Диагностика действующих параметров sshd через sshd -T
Многие правят /etc/ssh/sshd_config, а потом удивляются, что ничего не изменилось. Конфиг может включать Include /etc/ssh/sshd_config.d/*.conf, и там один из файлов переопределяет ваше значение. Поэтому правильный путь - читать итоговую эффективную конфигурацию, а не текстовый файл.
# Дамп итоговой эффективной конфигурации sshd с нормалиацией имён опций
sshd -T | egrep -i 'usedns|gssapiauthentication|logintimeout|dns'
Вывод (условный пример на RHEL-подобной системе):
usedns yes
gssapiauthentication yes
Вот и ответ. sshd -T честно показывает то, что sshd реально применяет при старте, а не то, что написано в комментариях конфига. Дальше проверяем синтаксис перед перезагрузкой:
# Проверить синтаксис конфигов без перезапуска sshd
sshd -t
systemctl reload sshd # мягкая перезагрузка без обрыва сессий
# в системах с systemd сокет-активацией: systemctl reload ssh.socket
Если sshd -t молчит - значит синтаксис корректен. Если ругается - например "Bad configuration option" или "line 42" - правим до перезагрузки, иначе рискуем оставить сервер без доступа.
Исправление медленного входа через UseDNS no в sshd_config
Само по себе UseDNS включает обратный DNS-запрос (PTR) для IP клиента и сравнение имени с прямым (A) запросом. Это защитный механизм против подмены имён, но в типовой корпоративной сети без настроенных PTR-записей он даёт только одну "пользу" - ту самую тридцатисекундную паузу. Отключаем:
# Отключаем reverse DNS lookup на стороне sshd
# редактируем файл либо в основном конфиге, либо в drop-in
echo 'UseDNS no' > /etc/ssh/sshd_config.d/10-disable-usedns.conf
sshd -t && systemctl reload sshd
После отключения sshd перестанет звонить в resolver при каждом подключении и вход станет мгновенным. На заметку: директива Match может переопределить UseDNS для отдельных сетей - sshd -T с аргументом -C покажет эффективное значение для конкретного подключения:
# Показать эффективные параметры для подключения с конкретного IP
sshd -T -C user=admin,host=ws-07,addr=192.168.1.50 | grep -i usedns
Если PTR-записи всё же нужны (например для host-based ACL по именам), то альтернатива - поднять нормальную обратную зону на своём DNS или прописать статический /etc/hosts на сервере:
# Быстрый костыль до нормальной PTR-зоны: статический обратный резолв клиентов
echo '192.168.1.50 ws-07.example.internal ws-07' >> /etc/hosts
Исправление задержки через GSSAPIAuthentication no и поведение клиента
Вторая классическая причина лагов - GSSAPI, обёртка для Kerberos-аутентификации. В Debian и Ubuntu клиент ssh по умолчанию ходит GSSAPIAuthentication yes, да и на серверах с sssd/adjoin она тоже включена. Если KDC недоступен (а в смешанной сети без Kerberos его просто нет), попытка обмена билетами замирает до таймаута.
# Отключаем GSSAPI на стороне сервера
echo 'GSSAPIAuthentication no' > /etc/ssh/sshd_config.d/11-disable-gssapi.conf
sshd -t && systemctl reload sshd
На клиенте при необходимости то же самое в /etc/ssh/ssh_config или в персональном ~/.ssh/config:
# ~/.ssh/config - отключаем GSSAPI глобально на клиенте
Host *
GSSAPIAuthentication no
Проверка после правок: повторяем ssh -vvv и убеждаемся, что в "Authentications that can continue" больше нет gssapi-методов и подключение отрабатывает за доли секунды.
systemd-logind и прочие скрытые тормоза при входе
Бывает, что UseDNS и GSSAPI выключены, а пауза осталась. Тогда подозреваем слои ниже sshd. Первый кандидат - systemd-logind: на некоторых системах зависшая или перегруженная служба логина добавляет паузу в 25-30 секунд перед появлением приглашения. Диагность такая:
# Смотрим, жив ли logind и нет ли у него очереди
systemctl status systemd-logind --no-pager
loginctl list-sessions
Если loginctl тоже подвисает - вот и виновник. Помогает systemctl restart systemd-logind (внимательно, активные сессии локальных пользователей могут обидеться). Длинная пауза может греться и на dbus-ошибках, и на полном /run или /var/log (да, заполненный tmpfs тоже умеет ставить вход раком), и даже на nscd с битым кэшем.
Ещё кандидаты на роль тормоза при SSH-входе: PAM-модули, пробующие сетевые проверки (pam_ldap/pam_sss без доступа к серверу каталогов), banner на nfs-шаре, мотd из сети, PAM-модуль pam_lastlog2 на монструозном wtmp. Быстрая проверка на сетевые модули:
# Ищем сетевые PAM-модули в стеке sshd
grep -E 'ldap|sss|winbind|krb5' /etc/pam.d/sshd /etc/pam.d/common-* 2>/dev/null
Тонкая настройка самого резолвера чтобы DNS не тормозил вход.
Когда PTR-зону поднять нельзя, а UseDNS отключать не хочется, спасением становится настройка таймаутов в /etc/resolv.conf. По умолчанию glibc-resolver ждёт пять секунд на попытку и делает две попытки на каждый сервер имён из списка. Три nameserver-строки превращаются в максимум тридцать секунд ожидания на одну попытку обратного резолва, и это ровно та цифра из жалоб пользователей. Опции в конце файла сжимают это ожидание до приемлемых рамок:
options timeout:1 attempts:1 rotate
Параметр timeout:1 сокращает ожидание ответа до одной секунды, attempts:1 запрещает повторные попытки, а rotate заставляет резолвер ходить по серверам имён по кругу вместо того, чтобы каждый раз упираться в первый, возможно недоступный, адрес. С такой строкой обратный запрос, которому неоткуда ответить, отваливается максимум за пару секунд, и вход по SSH перестаёт быть медитативной практикой. Честно говоря, это полумера: правильнее починить саму зону или выключить UseDNS, но в больших сетях, где DNS обслуживает соседний отдел, полумера спасает нервы до следующего квартала.
Отдельная засада живёт в dual-stack окружениях. Резолвер по умолчанию спрашивает и A, и AAAA записи, и если IPv6-маршрутизации в сети нет, а фильтр тихо роняет пакеты, каждая попытка выглядит как дополнительный пропущенный таймаут. Проверяется это просто: getent hosts сначала с явным запросом IPv4-имени, потом с IPv6, и сравнивается время. Разница в секунды - верный признак того, что виноват не sshd, а маршрутизация, и дальше разговор идёт уже со специалистом по сети, а не с конфигом sshd.
Убедиться, что тормозит именно resolver, помогает strace на живом процессе: strace -f -p $(pgrep -x sshd | head -1) -e trace=network,sendto,recvfrom во время нового подключения показывает, как dns-запрос улетает в порт 53 и сколько времени ответа нет. Картина с poll, который висит до ETIMEDOUT, закрывает вопрос окончательно и не оставляет сомнений, куда править.
Пошаговый план действий и профилактика рецидивов
Короткий маршрут, который экономит часы на проде:
- Замерить задержку на клиентах ssh -vvv и зафиксировать строку, после которой пауза.
- Проверить резолвер командой time getent hosts <IP_клиента> - если 30 секунд, начать с DNS.
- Снять эффективную конфигурацию sshd -T и проверить UseDNS и GSSAPIAuthentication.
- Отключить UseDNS no и GSSAPIAuthentication no drop-in-конфигами, проверить sshd -t, reload.
- Если осталось, смотреть systemd-logind, PAM-стек, свободное место в /run и /var/log.
Профилактика проста и честна. Следите за reachability DNS: если серверы имён меняются, меняйте их сразу на всех хостах, иначе старые мёртвые nameserver-строки вернут тридцать секунд каждому новому пользователю. Держите drop-in-конфиги в sshd_config.d под контролем версий - тогда причина паузы легко находится git blame-ом. Наконец, добавьте в мониторинг простой smoke-тест: замер времени ssh -o BatchMode=yes user@host true раз в минуту. Всё, что растёт выше секунды - повод разбудить администратора раньше пользователей.
После правок вход на проблемный сервер сокращается с тридцати секунд до полусекунды. И самое приятное: sshd не умничает больше, чем нужно, резолвер работает на здоровую сеть, а администратор получает назад своё время, нервы и уважение со стороны коллег, которые заметили, что подключение наконец перестало думать.