Работа по SSH в нестабильной сети: tmux, mosh и ControlMaster

, 7 мин чтения

Если вы работаете по SSH из поезда, с мобильного интернета или через нестабильный Wi-Fi, обрывы неизбежны. Плохо не само переподключение, а то, что вместе с соединением умирают запущенные команды, вывод и открытые редакторы. Три инструмента решают это с разных сторон: tmux сохраняет процессы на сервере, mosh сохраняет сам терминал при смене сети, а ControlMaster ускоряет повторные подключения.

Что теряется при обрыве

Когда SSH-соединение рвётся, сервер закрывает терминал сессии, и оболочка получает сигнал SIGHUP. Вместе с ней обычно завершаются запущенные из неё процессы: сборка, миграция, tail -f, редактор с несохранённым файлом. Если соединение не рвётся явно, а просто перестаёт отвечать, терминал выглядит зависшим, пока клиент не поймёт, что связи нет.

Отсюда два разных требования. Первое: процессы должны жить независимо от соединения. Второе: сам терминал должен переживать смену сети и короткие пропадания связи. Первое закрывает tmux, второе mosh, и вместе они работают лучше всего.

tmux: сессия живёт на сервере

tmux запускает оболочку внутри своего серверного процесса, который не зависит от SSH. Вы отключились, а процессы продолжают работать, и при следующем входе вы возвращаетесь в ту же сессию. Установка: sudo apt install tmux или sudo dnf install tmux.

tmux new -s work        # новая сессия с именем work
tmux ls                 # список сессий
tmux attach -t work     # подключиться к сессии
tmux new -A -s work     # подключиться, а если сессии нет, создать

Основные сочетания клавиш, префикс по умолчанию Ctrl-b:

КлавишиДействие
Ctrl-b dотключиться от сессии, оставив её работать
Ctrl-b cновое окно
Ctrl-b n, Ctrl-b pследующее и предыдущее окно
Ctrl-b %разделить окно на левую и правую панели
Ctrl-b "разделить окно на верхнюю и нижнюю панели
Ctrl-b [режим прокрутки и копирования, выход клавишей q

Чтобы попадать в сессию сразу при входе, передайте команду в SSH:

ssh -t user@host tmux new -A -s work

Или заведите для этого отдельный псевдоним в ~/.ssh/config:

Host work-tmux
  HostName host.example.com
  RemoteCommand tmux new -A -s work
  RequestTTY yes

Давайте сессиям понятные имена по проектам или задачам: deploy, logs, api. Тогда tmux ls сразу показывает, что где запущено, а ненужную сессию легко закрыть командой tmux kill-session -t logs. Сессия живёт, пока работает сервер: после перезагрузки машины её не будет, поэтому всё действительно важное, вроде фоновых служб, оформляйте как службы systemd, а не как окно tmux.

Если на сервере вместо tmux есть только screen, логика та же: screen -S work создаёт сессию, Ctrl-a d отключает от неё, screen -r work возвращает, screen -ls показывает список.

mosh: терминал, который переживает смену сети

mosh входит на сервер по обычному SSH, запускает там mosh-server и дальше обменивается данными с ним по UDP. Сессия не привязана к вашему IP-адресу: можно переключиться с Wi-Fi на мобильный интернет или закрыть ноутбук и открыть его позже, и mosh продолжит работу сам. На медленных каналах он показывает набранные символы сразу, не дожидаясь ответа сервера: это локальное эхо с предсказанием.

mosh нужно установить на обеих сторонах: sudo apt install mosh на сервере, brew install mosh на macOS. Официального нативного клиента для Windows нет, проще всего запускать mosh внутри WSL. Учтите, что первый вход всё равно идёт по SSH, поэтому порт SSH на сервере должен быть доступен, а ключи настроены как обычно. Затем откройте на сервере UDP-порты. По умолчанию mosh-server занимает порт в диапазоне от 60000 до 61000:

sudo ufw allow 60000:61000/udp

Если сессий немного, хватит узкого диапазона, например 60000:60010. Подключение:

mosh user@host
mosh --ssh="ssh -p 2222" user@host     # SSH на нестандартном порту
mosh -p 60001 user@host                # конкретный UDP-порт на сервере

mosh требует локаль UTF-8 на обеих сторонах. Если он отказывается запускаться с жалобой на локаль, проверьте вывод locale на сервере и задайте, например, LANG=C.UTF-8 или LANG=en_US.UTF-8. Поведение локального эха меняет флаг --predict: значение always включает его даже на быстрых каналах, never отключает.

ControlMaster: одно соединение на много сессий

Каждое новое SSH-подключение заново проходит рукопожатие и аутентификацию. Если вы часто открываете второй терминал, запускаете scp или работаете с git по SSH на одном и том же сервере, это ожидание накапливается, особенно при большой задержке. Мультиплексирование позволяет держать одно основное соединение и пускать через него все следующие:

Host *
  ControlMaster auto
  ControlPath ~/.ssh/sockets/%C
  ControlPersist 10m

Создайте каталог для сокетов: mkdir -p ~/.ssh/sockets && chmod 700 ~/.ssh/sockets. Значение auto использует существующее основное соединение, а если его нет, создаёт новое. %C подставляет хеш от имени хоста, порта, пользователя и других параметров подключения, поэтому путь к сокету получается коротким и уникальным. ControlPersist 10m оставляет основное соединение в фоне на десять минут после закрытия последней сессии. Значение yes держит его бессрочно, но в нестабильной сети разумнее ограничить время, чтобы не копить повисшие соединения. Мультиплексирование ускоряет и mosh: его первый вход идёт через обычный ssh, и если основное соединение уже открыто, этот шаг проходит быстрее.

Управление основным соединением:

ssh -O check host     # живо ли основное соединение
ssh -O exit host      # закрыть его
ssh -S none host      # отдельное подключение без мультиплексирования

Keepalive и autossh для туннелей

В нестабильной сети важно быстро замечать мёртвое соединение. Добавьте в ~/.ssh/config:

Host *
  ServerAliveInterval 30
  ServerAliveCountMax 4

Клиент раз в 30 секунд проверяет связь внутри зашифрованного канала и закрывает соединение после четырёх проверок без ответа. Вместе с ControlMaster это особенно важно: если основное соединение повисло, все сессии через него тоже висят, пока оно не закроется.

Проброс портов через ssh -L после обрыва сам не восстанавливается. Для долгоживущих туннелей используйте autossh: он перезапускает ssh, когда тот завершается.

autossh -M 0 -f -N \
  -o "ServerAliveInterval 30" -o "ServerAliveCountMax 3" \
  -o "ExitOnForwardFailure yes" -o "ControlPath none" \
  -L 5432:localhost:5432 user@host

-M 0 отключает собственный контрольный порт autossh, и он полагается на ServerAliveInterval: ssh сам завершится на мёртвом соединении, а autossh поднимет новое. -f уводит процесс в фон, -N не запускает команду на сервере, ExitOnForwardFailure завершает ssh, если порт пробросить не удалось. ControlPath none отключает мультиплексирование, чтобы туннель держал собственное соединение и не зависел от других сессий. Если самая первая попытка подключения неудачна, autossh по умолчанию не повторяет её. Переменная AUTOSSH_GATETIME=0 включает повторы и в этом случае, это удобно при запуске во время загрузки системы.

Как совместить инструменты

СитуацияЧто использовать
ноутбук в поездках, мобильный интернетmosh и внутри него tmux
стабильная сеть, много подключений к одним серверамControlMaster и keepalive
долгие задачи: сборка, миграции, деплойвсегда внутри tmux
проброс порта к базе или внутреннему сервисуautossh с -L

Самая устойчивая связка для интерактивной работы: mosh для транспорта и tmux для процессов. Одна команда подключается и сразу открывает сессию:

mosh user@host -- tmux new -A -s work

Даже если mosh прервётся окончательно, например mosh-server завершился по таймауту, процессы в tmux останутся, и вы вернётесь к ним обычным tmux attach.

Подводные камни

  • mosh не поддерживает проброс портов, X11 и пересылку SSH-агента. Для туннелей держите отдельное подключение через ssh -L или autossh.
  • mosh синхронизирует только видимую часть экрана, прокрутки назад в нём нет. Историю вывода храните в tmux.
  • В tmux история прокрутки по умолчанию ограничена 2000 строк. Увеличьте её в ~/.tmux.conf строкой set -g history-limit 50000, значение действует для новых окон. Строка set -g mouse on включает прокрутку колесом мыши.
  • Если UDP-порты на сервере закрыты, mosh не получит ответа от сервера и сообщит об этом. Проверьте межсетевой экран сервера и сети, где он стоит.
  • Если клиент пропал навсегда, mosh-server продолжает ждать его на сервере. Переменная MOSH_SERVER_NETWORK_TMOUT задаёт, через сколько секунд без связи с клиентом он завершится.
  • Во вложенном tmux, например локальном и удалённом, префикс перехватывает внешний экземпляр. Чтобы отправить его внутреннему, нажмите Ctrl-b дважды.
  • С RemoteCommand в ~/.ssh/config перестают работать scp и sftp через тот же псевдоним хоста. Поэтому автозапуск tmux держите в отдельном псевдониме, как в примере выше.

Если обрывы происходят постоянно даже на хорошей локальной сети, дело в маршруте до сервера. Здесь помогает защищённое подключение через ближайшую к серверу локацию.

Коротко

  • Долгие задачи запускайте только внутри tmux new -A -s work.
  • Отключайтесь через Ctrl-b d, возвращайтесь командой tmux attach -t work.
  • Для мобильного интернета и смены сетей поставьте mosh и откройте UDP-порты с 60000 по 61000.
  • Подключайтесь связкой mosh user@host -- tmux new -A -s work.
  • Включите ControlMaster auto, ControlPath с %C и ControlPersist для быстрых повторных подключений.
  • Держите ServerAliveInterval 30 и ServerAliveCountMax 4, чтобы мёртвое соединение закрывалось само.
  • Туннели поднимайте через autossh -M 0 с ExitOnForwardFailure yes.
  • Увеличьте history-limit в tmux: mosh прокрутку не хранит.

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

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

Все тарифы