SSH 로 붙어 있다가 잠깐 손을 놓으면 다음과 같이 끊긴다.
client_loop: send disconnect: Broken pipe
긴 작업을 걸어 두고 자리를 비우면 돌아왔을 때 세션이 사라져 있다.
이 메시지는 클라이언트가 쓰기를 시도했는데 TCP 연결이 이미 없어졌다는 뜻이다. 끊은 주체는 SSH 가 아니라 대개 중간 장비다.
방화벽 · NAT · L4 는 세션 표를 들고 있고, 일정 시간 패킷이 오가지 않으면 그 항목을 버린다. 이후에 오는 패킷은 알 수 없는 연결로 취급돼 버려지거나 RST 로 응답받는다. 양쪽 호스트는 자기가 끊은 적이 없으므로 다음 입력 때 비로소 알아차린다.
서버가 의도적으로 끊는 경우도 있다. ClientAliveInterval·ClientAliveCountMax 로 응답 없는 세션을 정리하거나, 셸의 TMOUT 이 유휴 셸을 종료시킨다.
주기적으로 빈 패킷을 보내 세션 표를 살려 둔다. ~/.ssh/config 에 둔다.
Host *
ServerAliveInterval 30
ServerAliveCountMax 6
TCPKeepAlive yes
30초마다 확인 패킷을 보내고, 6번 연속 응답이 없으면 그때 끊는다. 즉 실제로 죽은 연결은 3분 안에 정리되고, 살아 있는데 조용한 연결은 유지된다.
명령마다 주려면 다음과 같다.
ssh -o ServerAliveInterval=30 -o ServerAliveCountMax=6 user@host
ServerAliveInterval 은 SSH 계층에서 동작하므로 암호화 채널 안을 지나간다. TCPKeepAlive 는 TCP 계층이라 중간 장비 정책에 따라 무시될 수 있다. 둘 중 앞의 것이 더 확실하다.
/etc/ssh/sshd_config 를 고치고 데몬을 다시 읽힌다.
ClientAliveInterval 60
ClientAliveCountMax 5
sudo sshd -t && sudo systemctl reload sshd
반대로 유휴 세션을 정리하는 것이 정책이라면 이 값을 짧게 둔다. 이때 클라이언트의 ServerAliveInterval 로는 우회되지 않는다. 서버가 먼저 끊기 때문이다.
어느 단계에서 끊기는지 로그로 본다.
ssh -vvv user@host
서버 쪽 로그는 배포판마다 위치가 다르다.
sudo journalctl -u sshd -f
sudo tail -f /var/log/secure # RHEL 계열
sudo tail -f /var/log/auth.log # 데비안 계열
기록을 더 자세히 남기려면 sshd_config 의 LogLevel 을 VERBOSE 나 DEBUG 로 올린다. DEBUG 는 운영에서 오래 켜 두지 않는다.
키 교환 단계에서 곧바로 끊긴다면 이야기가 다르다. kex_exchange_identification 은 접속 제한이나 잘못된 포트 쪽이므로 유휴 타임아웃과 무관하다.
근본 대책은 작업을 세션에 매지 않는 것이다.
tmux new -s work
# 떼어내기: Ctrl-b d
tmux attach -t work
nohup 이나 setsid 로 띄우면 셸이 사라져도 프로세스는 남는다.
nohup ./long_job.sh > job.log 2>&1 &
이미 띄운 프로세스를 나중에 떼어 내려면 disown 을 쓴다.
ServerAliveInterval 을 너무 짧게(예: 5초) 두면 접속이 많은 게이트웨이에서 불필요한 트래픽이 늘어난다. 30~60초면 충분하다.
키 교환 중 끊기는 문제를 유휴 타임아웃으로 오인해 ServerAliveInterval 만 올리면 시간만 버린다. 메시지를 정확히 읽는다.
kex_exchange_identification 쪽 원인.