SSH-сессия обрывается при простое: как настроить keepalive
Знакомая картина: оставили терминал на десять минут, вернулись, а он не реагирует на ввод. Через какое-то время приходит client_loop: send disconnect: Broken pipe или Connection reset by peer. Чаще всего сервер тут ни при чём: соединение тихо умерло где-то по дороге, пока по нему не шли данные.
Что означают сообщения об ошибке
Текст ошибки подсказывает, кто закрыл соединение и почему. Вот самые частые варианты.
| Сообщение | Что произошло |
|---|---|
| client_loop: send disconnect: Broken pipe | Клиент попытался отправить данные в соединение, которого уже нет. Типичный итог долгого простоя или сна ноутбука |
| packet_write_wait: Connection to ... port 22: Broken pipe | То же самое в старых версиях OpenSSH |
| Connection reset by peer | Сервер или устройство по пути ответили сбросом TCP: запись в NAT удалена, сработал межсетевой экран или перезапустился sshd |
| Timeout, server ... not responding. | Сработала проверка ServerAliveInterval: сервер не ответил несколько раз подряд, и клиент закрыл сессию сам |
| Connection to ... closed by remote host. | Сессию закрыл сервер: таймаут ClientAliveInterval, перезапуск sshd или автоматический выход из оболочки |
| timed out waiting for input: auto-logout | Сработала переменная TMOUT в bash на сервере. Это не сеть, а политика выхода по бездействию |
Почему соединение рвётся при простое
Между вами и сервером стоят устройства с NAT и межсетевые экраны: домашний роутер, оборудование мобильного оператора, корпоративная сеть, Wi-Fi в кафе. Каждое из них хранит таблицу открытых соединений, и запись в ней живёт ограниченное время. Если по SSH долго не идут пакеты, запись удаляется. Следующий пакет от вас уже никуда не доходит, а ответ сервера не находит адресата.
Таймауты у всех устройств разные, и узнать их заранее обычно нельзя. У некоторых счёт идёт на минуты, особенно в мобильных сетях и гостевом Wi-Fi.
Клиент узнаёт о разрыве поздно: TCP ещё какое-то время повторяет отправку, поэтому терминал выглядит зависшим, а не закрытым. Отсюда и знакомая пауза перед Broken pipe.
Особенно часто это бьёт по туннелям вида ssh -L 5432:localhost:5432. Клиент базы данных молчит между запросами, туннель простаивает и тихо умирает, а приложение потом получает ошибку соединения с базой.
Как диагностировать
- Отметьте, когда именно рвётся. Если только после простоя, причина почти наверняка в NAT. Если посреди активной работы, смотрите на качество канала.
- Проверьте, не уходил ли ноутбук в сон. При сне сетевой интерфейс отключается, и никакие настройки keepalive сессию не сохранят.
- Посмотрите, какие параметры реально применяются:
ssh -G user@host | grep -i alive. Команда выводит итоговую конфигурацию с учётом всех блоковHost. - Подключитесь с подробным логом
ssh -v user@hostи после обрыва прочитайте последние строки: видно, кто и на каком этапе закрыл соединение. - На сервере загляните в журнал:
journalctl -u ssh --since "1 hour ago"(в некоторых системах служба называетсяsshd). - Выполните на сервере
echo $TMOUT. Если там число, оболочка сама завершает сессию после этого количества секунд без ввода.
Настройка на стороне клиента
Добавьте в ~/.ssh/config:
Host *
ServerAliveInterval 30
ServerAliveCountMax 4ServerAliveInterval задаёт, как часто клиент отправляет служебный пакет по зашифрованному каналу, если от сервера ничего не приходило. По умолчанию он равен нулю, то есть проверки выключены. ServerAliveCountMax определяет, сколько ответов подряд можно не получить до разрыва, по умолчанию 3. С такими значениями живое соединение не засыпает, а мёртвое закрывается примерно через две минуты вместо бесконечного зависания.
Порядок блоков важен: для каждого параметра OpenSSH берёт первое найденное значение. Поэтому Host * ставьте в конец файла, а настройки конкретных серверов выше:
Host prod
HostName 203.0.113.10
User deploy
ServerAliveInterval 15
Host *
ServerAliveInterval 30
ServerAliveCountMax 4Для prod интервал будет 15 секунд, для остальных серверов 30. Если поменять блоки местами, Host * задаст 30 секунд всем, включая prod. Для разовой проверки параметр можно передать прямо в команде: ssh -o ServerAliveInterval=30 user@host.
Особенности macOS, Linux и Windows
- macOS и Linux: файл
~/.ssh/config. Он не должен быть доступен на запись другим пользователям, иначе ssh ответитBad owner or permissions. Надёжнее всего выполнитьchmod 600 ~/.ssh/config. - Windows 10 и 11: встроенный клиент OpenSSH читает
C:\Users\<имя>\.ssh\config, синтаксис тот же. - PuTTY: в разделе Connection задайте
Seconds between keepalives, например 30, и сохраните сессию. - VS Code Remote SSH использует системный клиент OpenSSH и тот же файл конфигурации, так что настройка сработает и там.
- Сон ноутбука keepalive не лечит. На macOS команда
caffeinate -i ssh user@hostне даст системе уснуть по бездействию, пока идёт сессия, на Linux то же делаетsystemd-inhibit ssh user@host. Закрытие крышки это не отменяет.
Настройка на сервере
Если у вас есть доступ к серверу, включите проверки и с его стороны. Тогда сервер сам будет поддерживать трафик в канале и закрывать сессии, клиент которых пропал. Во многих современных дистрибутивах, включая Debian, Ubuntu и Fedora, основной конфиг подключает каталог /etc/ssh/sshd_config.d/. Удобно положить туда отдельный файл, например keepalive.conf, а в остальных системах дописать строки в /etc/ssh/sshd_config:
ClientAliveInterval 30
ClientAliveCountMax 4Проверьте синтаксис, перечитайте конфигурацию и убедитесь, что значения применились:
sudo sshd -t
sudo systemctl reload ssh # в RHEL, Fedora и Arch служба называется sshd
sudo sshd -T | grep -i clientalivesshd -t ловит опечатки до перезагрузки, а sshd -T показывает итоговые значения. Это важно, потому что в sshd тоже действует правило первого значения: строка в подключённом файле перекроет то, что написано ниже в основном конфиге.
Почему не TCPKeepAlive
Параметр TCPKeepAlive включён по умолчанию, но для этой задачи он почти бесполезен. Он опирается на механизм keepalive операционной системы, а в Linux первая проба по умолчанию уходит через 7200 секунд простоя (net.ipv4.tcp_keepalive_time). Запись в таблице NAT к этому времени давно удалена. Кроме того, эти пакеты идут вне шифрования, и документация OpenSSH прямо называет их подделываемыми. Проверки ServerAlive и ClientAlive идут внутри защищённого канала, и их интервал вы задаёте сами.
Если сеть сама по себе нестабильна
- Запускайте долгие задачи в
tmuxилиscreen: сессия живёт на сервере, после переподключения достаточноtmux attach. Именованная сессия создаётся командойtmux new -s work, отсоединиться можно клавишамиCtrl+b, затемd. - Для мобильного интернета и переездов между сетями попробуйте
mosh: он переживает смену IP-адреса и короткие пропадания связи. Ему нужны открытые на сервере UDP-порты, по умолчанию из диапазона от 60000 до 61000. - Не запускайте миграции и деплой в терминале без
tmux: обрыв на середине оставит систему в непредсказуемом состоянии. - Команды, которые должны пережить выход из сессии, запускайте через
nohupили оформляйте отдельной службой systemd. - Для долгоживущих туннелей используйте
autossh: он перезапускает ssh после разрыва. С ключом-M 0он полагается на те жеServerAliveIntervalиServerAliveCountMax, а-o ExitOnForwardFailure=yesне даст ssh молча работать без проброшенного порта.
Когда виновата не настройка, а канал
Если обрывы продолжаются даже с keepalive и во время активной работы, проблема в самом пути до сервера: потери пакетов в публичном Wi-Fi или нестабильный маршрут. Запустите mtr -rwc 100 your-server.example и смотрите на последнюю строку, то есть на сам сервер. Если потери начинаются на одном из промежуточных узлов и сохраняются до конца маршрута, проблема на этом участке. Потери только на промежуточном узле при чистой последней строке обычно означают, что узел просто ограничивает ответы на диагностические пакеты.
Здесь помогает защищённое подключение через страну, ближайшую к серверу: трафик идёт по одному устойчивому каналу, а не через случайный набор узлов.
Коротко
- Добавьте
ServerAliveInterval 30иServerAliveCountMax 4в~/.ssh/config, блокHost *держите в конце файла. - На своём сервере включите
ClientAliveIntervalи проверьте результат черезsshd -T. - Не полагайтесь на
TCPKeepAlive: его пробы уходят слишком редко. - Если сессия закрывается всегда через одно и то же время, проверьте
TMOUTна сервере. - Долгие задачи запускайте в
tmux, в мобильной сети попробуйтеmosh. - Если рвётся и под нагрузкой, ищите потери пакетов через
mtr.