You come back to a terminal you left ten minutes ago, press a key, and get client_loop: send disconnect: Broken pipe. Or Write failed: Broken pipe. Or nothing at all, just a cursor that no longer responds. Whatever was running is gone, and you start again. There are two separate fixes here and they solve different problems, which is why applying one and still having trouble is so common.
Why does my SSH session keep disconnecting?
Four causes cover almost every case, and they are not equally common.
- An idle timeout closed it deliberately. Many sshd configs set
ClientAliveIntervalandClientAliveCountMax, and plenty of corporate firewalls and load balancers cut idle TCP connections after a fixed window. This is the only cause where something intentionally decided to close your session. - A NAT or firewall state table forgot about you. Every router between you and the host tracks open connections in a table with finite size and a timeout, often around five minutes of silence. When your entry is evicted, neither end is notified. Both still believe the connection is open. This is the most common cause and it produces no error until you try to send something.
- Your network changed underneath the connection. A TCP connection is identified by source address, source port, destination address, and destination port. Change the source address and it is, by definition, a different connection. Moving from wifi to cellular, switching access points, or a DHCP lease renewal all kill every open session instantly.
- The client machine slept. A closed laptop lid or a backgrounded phone app stops servicing the socket, and by the time it wakes the other end has usually given up.
Notice that three of the four kill the connection without either side sending a close. That silence is what makes the failure confusing.
What does "client_loop: send disconnect: broken pipe" actually mean?
It means your client tried to write to a socket the kernel already knew was dead, and got EPIPE back. That is all. The message is your client reporting the moment it discovered the problem, not the moment the problem happened.
This is why the error appears the instant you press a key after stepping away. The connection died at some point during the idle period, silently, in one of the ways above. Your client had no reason to notice, because it was not sending anything. The first keystroke is the first write, the write fails, and you see the error. People often conclude that typing caused the disconnect. It only revealed it.
The practical consequence: if you frequently see broken pipe after idle periods specifically, the cause is almost certainly state eviction or an idle timeout, and keepalives will genuinely fix it. If you see it immediately after moving between networks, keepalives will not help at all, because there is no keeping alive a connection whose source address no longer exists.
How do you keep an SSH session alive after disconnect?
Make the connection stop being idle. Add this to ~/.ssh/config on the machine you connect from:
Host *
ServerAliveInterval 60
ServerAliveCountMax 3
That sends an encrypted probe through the SSH channel every 60 seconds when nothing else is happening. Two things follow. Router and firewall state tables keep seeing traffic, so they stop evicting your entry. And if three consecutive probes go unanswered, your client gives up after about three minutes and tells you, instead of hanging until you press a key.
On a host you administer, the mirror image goes in /etc/ssh/sshd_config:
ClientAliveInterval 60
ClientAliveCountMax 3
A note on a common piece of advice: TCPKeepAlive yes is not the same thing and is a weaker tool. It operates at the TCP layer, the probes are spoofable, and the default system interval is usually two hours, which is far too long to keep NAT state alive. Prefer ServerAliveInterval. It also works when SSH is running over a tunnel that terminates TCP somewhere in the middle.
Keepalives are worth setting on every machine you use. They are cheap and they eliminate an entire category of annoyance. What they cannot do is save your work when the connection dies anyway, and on a mobile connection it eventually will.
Does tmux keep an SSH session alive?
No. tmux has no effect on the SSH connection whatsoever, and sessions will drop exactly as often with it as without it. What changes is the consequence.
Without tmux, your shell is a child of the SSH connection. When the connection dies, the shell gets a hangup signal and everything running in it goes down. With tmux, your shell is a child of the tmux server, which is an independent long-lived process on the remote host. SSH is only how you attach a terminal to it. When SSH dies, tmux notices nothing, the work continues, and you reattach later:
tmux new -s work
tmux attach -t work
So the two fixes address different halves of the problem. Keepalives try to prevent the drop. tmux makes the drop harmless. Anyone running long jobs over SSH wants both, and if you only do one, do tmux, because prevention is never complete.
The related tool worth knowing is Mosh, which replaces the SSH transport with a UDP protocol built for exactly this. Mosh survives roaming, sleep, and IP changes, because it does not identify a session by an IP pair. It genuinely solves the connection half in a way keepalives cannot. Its trade-offs are that it needs a UDP port range open, it has no native scrollback, and it does not help if the process itself dies with the shell, so most people run Mosh and tmux together rather than choosing.
What happens when the client is a phone?
Every problem above gets worse, and one of them becomes unsolvable at the connection layer.
A phone changes networks constantly. Walking out of the house hands you from wifi to cellular; walking into an office hands you back. Each handover changes your source address, which ends every open TCP connection immediately. No keepalive interval prevents this, because the connection is not idle, it is invalid.
A phone also aggressively suspends backgrounded apps. iOS gives an app a short grace period after it leaves the foreground and then stops scheduling it entirely. The socket is not serviced, the far end times out, and by the time you open the app there is nothing left to resume. This is not a limitation of any particular SSH client; it is the platform behaving as designed, and it applies to every app on the device.
So on a phone the connection is guaranteed to break, repeatedly, as a normal part of the day. Prevention is not on the table. The only thing that helps is making the break irrelevant, which means the work has to live on the host rather than inside the connection, exactly as tmux arranges. That is why the manual mobile SSH setup people converge on is always some combination of Mosh, tmux, and a lot of reattaching, and why the reattach ceremony is the part that wears thin: typing tmux attach -t work on a glass keyboard several times a day is a poor use of a pocket computer.
The short version
Set ServerAliveInterval 60 and ServerAliveCountMax 3 in your SSH config today; it costs nothing and removes the most common cause. Understand that broken pipe reports a death that already happened, so it points at idle state eviction rather than at whatever you just typed. Run long work inside tmux, because prevention is never complete. And accept that on a phone the connection will break no matter what, so the only durable answer is work that survives on the host.
Onepilot takes that last idea and builds the app around it: shells run in a daemon on the host rather than inside the phone's connection, so backgrounding the app, losing signal, or handing off from wifi to cellular leaves the session running, and reconnecting replays what you missed without a reattach command. This post covers connections that open and then die. The other two failure modes have their own answers: a connection that never opens at all is SSH connection refused, and one that opens but rejects your key is Permission denied (publickey). If you cannot reach the host in the first place because it sits behind NAT, see SSH without port forwarding. For the wider mobile workflow, see running agents on a server from your phone.
