OnepilotDownload
Onepilot mascot

Why Your SSH Session Keeps Disconnecting, and How to Keep It Alive

What causes SSH to drop, what client_loop: send disconnect: broken pipe actually means, how keepalives differ from tmux, and why neither fully solves it when the client is a phone.

sofiane8910

by sofiane8910 · July 25, 2026 · 8 min read

Onepilot runs these agents from your iPhone, download it on the App Store.

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.

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.

FAQ

Why does my SSH session keep disconnecting?

Usually one of four things. An idle timeout on the server or an intermediate firewall closed the connection because nothing was sent for a while; a NAT or firewall state table dropped the entry for an idle connection without telling either end; your network changed, so the source address the connection was built on no longer exists; or the machine you were connecting from went to sleep. Only the first is a real timeout. The others are silent deaths that you discover when you next press a key.

What does client_loop: send disconnect: broken pipe mean?

It means your SSH client tried to write to a socket that was already dead, and the operating system told it the pipe was broken. The connection did not fail at that moment; it failed earlier, quietly, and the client only found out when it attempted to send something. That is why the error so often appears the instant you start typing after a break. The message describes the discovery, not the cause.

How do you keep an SSH session alive after disconnect?

Add ServerAliveInterval 60 and ServerAliveCountMax 3 to your ~/.ssh/config. That makes your client send a small encrypted probe every 60 seconds, which keeps NAT and firewall state alive and lets the client notice a dead connection within about three minutes rather than hanging. On the server side, ClientAliveInterval does the same in the other direction. This keeps the connection alive; it does not save the work if the connection dies anyway.

Does tmux keep an SSH session alive?

No, and this is the most useful misconception to clear up. tmux does nothing to the SSH connection, which will still drop exactly as often. What tmux does is decouple your work from the connection: your shell and everything running in it live inside a tmux server on the remote host, so when SSH dies the work keeps running and you reattach to it later. Keepalives try to prevent the drop. tmux makes the drop stop mattering.

Related reading

Run your AI agents from your iPhone

Download Onepilot on the App Store.

See also: the three-layer agent overview, run Hermes on iPhone, or all articles.