Работа по SSH в нестабильной сети: tmux, mosh и ControlMaster
Если вы работаете по 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 прокрутку не хранит.