Короткий ответ
Если TCP/22 устанавливается и вы даже успеваете сделать uptime, а потом сессия умирает — это не «sshd не запущен». Классика: idle NAT/firewall (state timeout 300–900 с), ClientAliveInterval/ClientAliveCountMax на сервере, ServerAliveInterval на клиенте, промежуточный балансировщик. Реже — MaxStartups, LoginGraceTime на этапе баннера, или разрыв из‑за смены IP клиента. Сначала замерьте через сколько секунд и при каком простое, потом крутите keepalive, не Restart=always на ssh.
Хост host.example (10.0.20.10), пользователь admin.
Симптомы и как отличить
- сессия живёт, пока печатаете, умирает после кофе;
scpбольших файлов обрывается, интерактив работает — MTU/NAT ALG, не idle;- сразу после пароля/ключа — это не принимает подключение / AllowUsers;
- все протоколы к хосту рвутся — канал, не sshd.
| Сообщение | Частая причина |
|---|---|
| Broken pipe спустя ~5–15 мин idle | NAT/stateful FW |
| disconnect, reason 2, Timeout | ClientAlive на сервере |
| reset mid-transfer | MTU, conntrack |
| No route to host внезапно | клиент сменил Wi‑Fi/VPN |
Возможные причины
- Межсетевой экран режет idle TCP (типично 3600 с и меньше).
- На сервере
ClientAliveCountMax 0при ненулевом интервале ведёт себя иначе в разных версиях OpenSSH — читайтеsshd -T, не форум. - Клиент без
ServerAliveIntervalза NAT. TCPKeepAlive yesесть, но keepalive ядраtcp_keepalive_time=7200— слишком редко для NAT на 30 минут.MaxSessions/MaxStartupsпри множестве ControlMaster.unattended-upgradesперезапускает sshd (обычно не рвёт уже открытые, но socket activation на 24.04 имеет нюансы).- Смена времени на часы ломает Kerberos GSSAPI — NTP.
Диагностика
На клиенте точное время жизни:
date -u
ssh -vvv admin@10.0.20.10
# в другом окне
sleep 300Ищите в -vvv строки Timeout, wait time, Sending keepalive, packet_write_wait.
На сервере эффективные параметры:
sudo sshd -T | grep -Ei 'clientalive|tcpkeepalive|logingracetime|maxstartups|maxsessions|maxauthtries|usedns'Журнал разрыва:
sudo journalctl -u ssh -u ssh.socket --since '1 hour ago' --no-pager
sudo grep sshd /var/log/auth.log | tail -n 40Timeout, idle vs kex_exchange_identification — разные фазы.
Conntrack, если nft/iptables на этом же хосте:
sudo conntrack -L -p tcp --dport 22 2>/dev/null | head
sudo nft list ruleset | grep -i timeoutUFW сам по себе редко рвёт установленные, но REJECT новых при политике — другое. Проверьте, нет ли ufw limit ssh + шквал реконнектов.
Сеть и DNS обратного запроса: UseDNS yes (сейчас по умолчанию обычно no) может задерживать логин, не idle. Если логин долгий — DNS.
Дамп на 20 секунд вокруг обрыва (на jump-хосте):
sudo tcpdump -ni any host 10.0.20.10 and tcp port 22 -w /tmp/ssh.pcapИщите RST от промежуточного IP, не от 10.0.20.10.
Решение
Сценарий A. Idle NAT
На клиенте (ваш ноутбук), не обязательно на всех серверах:
# ~/.ssh/config
Host host.example
HostName 10.0.20.10
User admin
ServerAliveInterval 30
ServerAliveCountMax 3На сервере, если политика позволяет будить сессии:
# /etc/ssh/sshd_config.d/60-keepalive.conf
ClientAliveInterval 30
ClientAliveCountMax 3sudo sshd -t && sudo systemctl reload sshReload не должен рвать существующие сессии OpenSSH.
Сценарий B. Ядро TCP keepalive слишком редкое
Если обязаны использовать TCPKeepAlive:
sysctl net.ipv4.tcp_keepalive_time net.ipv4.tcp_keepalive_intvl net.ipv4.tcp_keepalive_probesСнижение tcp_keepalive_time — системный эффект на все TCP, документируйте.
Сценарий C. Обрыв больших копий
Проверьте MTU:
ip -br addr
ping -M do -s 1472 10.0.20.10PMTU чёрные дыры лечат clamp на роутере, не keepalive.
Сценарий D. Лимиты MaxStartups
В логе Maximum startup connections. Поднимите аккуратно или уберите шквал скриптов с новыми TCP вместо ControlMaster.
sudo sshd -T | grep maxstartupsСценарий E. VPN клиент рвёт при простое
Это idle VPN, не sshd. Держите VPN keepalive отдельно; SSH keepalive не спасёт, если tun0 умер.
Как замерить idle без догадок «это NAT»
На сервере в уже открытой сессии:
date +%s
# на клиенте оставьте ssh без ввода
# когда умрёт, снова date и вычислите дельтуЕсли дельта стабильно 300, 600 или 1800 секунд — ищите таймаут state table (часто 3600, но middlebox бывает 300). Если дельта ~ ClientAliveInterval * ClientAliveCountMax — это sshd. Сверьте sshd -T.
Для ControlMaster на ноутбуке админа обрыв multiplex-сокета рвёт все окна сразу — выглядит как «сервер выкинул всех». Смотрите ~/.ssh/config ControlPath.
Политика IPQoS af21 cs1 в новых OpenSSH иногда режется домашними AP. Эксперимент только в клиентском Host host.example: IPQoS none. Не ставьте это в sshd глобально без замера tcpdump DSCP.
Балансировщики L4 (NLB) с idle 350 с убивают SSH независимо от ClientAlive, если keepalive реже. Ставьте интервал заметно короче idle NLB (например 30 с при idle 350).
Как проверить, что проблема устранена
Оставьте сессию без ввода на время, большее прежнего обрыва + 5 минут:
ssh admin@10.0.20.10 'sleep 1200 && echo still-alive'Повторите с вашего NAT. still-alive должен печататься. Параллельно интерактивная сессия.
Если не помогло
- Рвётся ровно в 3600 с — ищите timeout балансировщика/облачного NLB.
- Рвётся только с одного провайдера: CGNAT.
- После
reload sshсразу всех выкинуло: вы сделалиrestartсокета на 24.04 — смотрите unit. - GSSAPI/Kerberos: часы, не keepalive.
Профилактика
- Единый фрагмент keepalive в
sshd_config.dдля jump-хостов. - Мониторинг длительности сессий не нужен; нужен runbook NAT timeout.
- ControlMaster на админских ноутбуках снижает шквал handshake.
- Не фильтровать ESTABLISHED случайным «переприменить ufw --force».
FAQ
ServerAlive на клиенте или ClientAlive на сервере?
Любой из двух обычно достаточен против NAT. Серверный ClientAlive ещё закрывает мёртвые клиенты. Иметь оба с разумными 30/3 нормально.
TCPKeepAlive vs ClientAlive?
TCPKeepAlive — пустые ACK ядра, часто редкие. ClientAlive — SSH-протокол, предсказуемее через NAT.
Почему tmux спасает?
Процесс на сервере живёт после обрыва TCP. Это не фикс NAT, это страховка работы. Всё равно чините keepalive.
autossh нужен?
Для туннелей — да. Для интерактива достаточно ServerAlive.
OpenSSH и IPQoS ломают Wi‑Fi?
Редко на старых WMM. Симптом — обрыв сразу, не idle. Проверка: IPQoS none в клиентском конфиге как эксперимент, не как глобальный hardening.