SSH-сессия обрывается при простое: как настроить keepalive

, 7 мин чтения

Знакомая картина: оставили терминал на десять минут, вернулись, а он не реагирует на ввод. Через какое-то время приходит 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 4

ServerAliveInterval задаёт, как часто клиент отправляет служебный пакет по зашифрованному каналу, если от сервера ничего не приходило. По умолчанию он равен нулю, то есть проверки выключены. 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 clientalive

sshd -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.

Стабильный канал для работы

Один ключ, 20 стран, до 5 устройств на Standard. Возврат в течение 7 дней.

Все тарифы