SSH connection keeps dropping: Broken pipe, resets and timeouts
An idle SSH session that dies with Broken pipe is almost always a network device forgetting the connection. Keepalives fix idle drops; tmux, mosh or a client that reattaches fix the rest.
Quick answer. Add a keepalive to ~/.ssh/config so idle connections are not dropped.
For drops keepalives cannot prevent, such as a network switch or a sleeping laptop, run your work inside
tmux and reattach after reconnecting.
Host *
ServerAliveInterval 30
ServerAliveCountMax 4
What the error messages mean
The message depends on your OpenSSH version and on which side noticed first. They all say the same thing: the TCP connection under your session is gone.
| Message | What happened |
|---|---|
client_loop: send disconnect: Broken pipe |
Current OpenSSH. The client wrote to a connection that had already been dropped, usually after the session sat idle. |
packet_write_wait: Connection to 203.0.113.10 port 22: Broken pipe |
The same thing, as older OpenSSH versions word it. |
Read from remote host example.com: Connection reset by peer |
Something sent a TCP reset: the server, a firewall or a NAT device that no longer knows the connection. |
Timeout, server example.com not responding. |
Your own ServerAliveInterval gave up after ServerAliveCountMax unanswered checks. The link was dead, and ssh said so. |
Connection to example.com closed by remote host. |
The server ended the session on purpose: an idle limit, a reboot, or sshd being restarted with a config that kills sessions. |
Connection timed out during banner exchange |
TCP connected but the server never sent its SSH greeting in time. This is a login problem, not a drop; see Troubleshooting. |
Note when it happens. A drop after a quiet spell of a few minutes points to an idle timeout. A drop when you move between networks, open the laptop lid or come back to your phone points to the connection itself being cut, which no setting on the connection can prevent.
Why connections drop
NAT and firewall idle timeouts
Home routers, office firewalls and mobile carriers translate addresses (NAT) and keep a table of open connections. An entry that sees no traffic for a while is deleted to save memory. Common limits range from a few minutes on carrier-grade NAT to an hour on home routers. An SSH session where nothing is printed and nothing is typed sends no packets at all, so the entry expires. The next keystroke goes nowhere or is answered with a reset.
Switching from Wi-Fi to mobile data
A TCP connection is tied to the IP addresses at both ends. When your device moves from Wi-Fi to mobile data, or between access points that hand out different addresses, its address changes and the old connection is orphaned. The server keeps waiting for packets from an address you no longer have.
Sleep, lid close and a locked phone
A sleeping laptop does not answer anything. Phones go further: they pause or restrict apps in the background to save battery, so an SSH client stops reading from its socket soon after you lock the screen. Whichever side gives up first, the session is gone by the time you return.
Server-side limits
A server can disconnect idle clients on purpose. Look for ClientAliveInterval with a low
ClientAliveCountMax in /etc/ssh/sshd_config, often set by hardening guides, or a shell
variable such as TMOUT=600 in /etc/profile, which logs out an idle bash with
timed out waiting for input: auto-logout. If that is the cause, talk to whoever runs the server before
working around a policy.
Fix 1: keepalives from the client
ServerAliveInterval makes ssh send a small encrypted message through the session after that many
seconds of silence. The traffic keeps NAT entries fresh, and the reply proves the server is still there. Add it to
~/.ssh/config for every host:
Host *
ServerAliveInterval 30
ServerAliveCountMax 4
With these values, a dead connection is detected after four unanswered checks, about two minutes, and ssh exits with
Timeout, server ... not responding instead of hanging forever. To try it once without editing the file:
ssh -o ServerAliveInterval=30 -o ServerAliveCountMax=4 [email protected]
On Windows the file is C:\Users\you\.ssh\config, and the built-in OpenSSH client reads the same
options. If the file did not exist, make sure only you can write to it, or ssh refuses it with
Bad owner or permissions; on Linux and macOS that is chmod 600 ~/.ssh/config.
Fix 2: keepalives from the server
The server has the mirror image, ClientAliveInterval and ClientAliveCountMax. It keeps the
path open for every user, including ones whose client has no keepalive, and it lets the server clean up sessions
whose client disappeared. Put it in a drop-in file, which current Debian, Ubuntu and Fedora include automatically:
sudo tee /etc/ssh/sshd_config.d/keepalive.conf <<'EOF'
ClientAliveInterval 60
ClientAliveCountMax 3
EOF
sudo sshd -t
sudo systemctl reload ssh # Debian and Ubuntu
sudo systemctl reload sshd # Fedora, RHEL, Arch
sshd -t checks the configuration first, so a typo cannot lock you out. Reloading keeps existing
sessions; the new values apply to the next login. If your distribution has no sshd_config.d folder,
add the two lines to /etc/ssh/sshd_config itself.
What TCPKeepAlive does and does not do
TCPKeepAlive yes is the default on both sides. It asks the operating system to send TCP-level probes,
but most systems wait two hours before the first one, far longer than any NAT timeout. The probes are also not
encrypted, so they can be spoofed. Leave it on, it does no harm, but rely on ServerAliveInterval and
ClientAliveInterval, which travel inside the encrypted session.
Fix 3: tmux or screen, so the shell survives
Keepalives cannot save a connection whose IP address changed or whose client was asleep. What you can do is make the connection unimportant: run your shell inside a terminal multiplexer on the server. The programs keep running when SSH drops, and you reattach after logging in again.
tmux new -s work # start a named session
# work as usual, then detach with Ctrl+b, then d
tmux ls # list sessions after reconnecting
tmux attach -t work # reattach
One command that attaches if the session exists and creates it otherwise:
ssh -t [email protected] tmux new -A -s work
GNU screen works the same way, with different keys:
screen -S work # start
# detach with Ctrl+a, then d
screen -ls # list
screen -r work # reattach
The trade-off is that tmux becomes part of your routine. You have to remember to start it, learn its prefix key and its scrollback mode (Ctrl+b then [), and install it on every server. Nested inside other tools it can also fight over key bindings.
Fix 4: mosh
mosh (mobile shell) logs in with SSH, starts mosh-server on the host and then talks to it over UDP. It
survives IP changes and sleep: when your device comes back, the session resumes. It also echoes your typing locally,
which helps on slow links.
sudo apt install mosh # on the server and the client
sudo ufw allow 60000:61000/udp # if the server has a firewall
mosh [email protected]
- Pros: roams between networks, recovers after sleep, responsive on high latency.
- Cons: needs UDP ports 60000 to 61000 open, which many corporate networks and cloud security groups block. No port forwarding, no agent forwarding and no file transfer. Scrollback is limited to what is on screen, so many people run tmux inside mosh anyway. The server needs a UTF-8 locale and the mosh package.
Fix 5: a client that keeps the session
The last option moves the work into the SSH client. Termphin, made by the team behind this site, uploads a small open-source helper to the server over SSH. The helper keeps your shell running while you are away, and when the connection comes back Termphin reattaches you where you left off, with the screen as it was. There is no tmux to install or remember, and nothing to open on the firewall: it all runs over the same SSH connection on port 22.
The helper is optional; without it Termphin is a plain SSH client, and the keepalive advice above applies to it like to any other. It is a good fit when the drops come from a phone being locked or a network switch, which is exactly the case keepalives cannot fix.
Troubleshooting
"Connection timed out during banner exchange"
The TCP connection opened but sshd did not send its greeting. Common causes: the server is overloaded or swapping,
too many unauthenticated connections hit MaxStartups (often bots), a firewall or fail2ban is half
blocking you, or a slow reverse DNS lookup with UseDNS yes. Try again with
ssh -v, check the server's load, and look at journalctl -u ssh (or -u sshd).
"kex_exchange_identification: read: Connection reset by peer"
The server, or something in front of it, closed the connection before the handshake. Usually fail2ban, a
hosts.deny rule, MaxStartups or the wrong port behind a load balancer. It is a login
problem, not a dropped session.
Keepalives are set but sessions still drop
Check that the option is actually applied: ssh -G example.com | grep -i serveralive prints the values
ssh will use. A more specific Host block earlier in the file wins over Host *, because ssh
takes the first value it finds. If the values are right, the drop is probably a network change or sleep, not an idle
timeout.
The session freezes instead of disconnecting
Without keepalives, ssh can wait for a long time on a dead connection. Press Enter, then type
~. to close it from the client side, then reconnect. With ServerAliveInterval set, a dead
link ends on its own within a couple of minutes.
Drops only through a VPN
VPNs add their own idle timeouts and can lower the packet size. Keep the keepalive, and if sessions stall when a lot of output is printed, the cause may be MTU: ask the VPN's administrator, or test with a smaller MTU on your interface.
FAQ
What does "client_loop: send disconnect: Broken pipe" mean?
Your ssh client tried to send data over a TCP connection that no longer exists. Something between you and the server, or the server itself, dropped the connection while it was idle, and ssh only noticed when it next wrote to it. Keepalives with ServerAliveInterval usually prevent it.
What is a good value for ServerAliveInterval?
Between 15 and 60 seconds. 30 with ServerAliveCountMax 4 is a sensible default: it keeps most NAT and firewall mappings open and gives up on a dead link after about two minutes. Very low values only add traffic.
Should I set keepalives on the client or on the server?
Either works for idle timeouts. The client setting is yours alone and needs no admin rights; the server setting protects every user and also lets the server clean up sessions whose client vanished. Setting both is fine.
Can keepalives keep a session alive when I switch from Wi-Fi to mobile data?
No. A network switch changes your IP address, and a TCP connection cannot move to a new address. You need something that survives a reconnect: tmux or screen on the server, mosh, or a client that reattaches for you.
Is mosh a replacement for SSH?
Only for the interactive terminal. It logs in over SSH and then switches to its own UDP protocol, so it needs mosh-server on the host and open UDP ports. It does not do port forwarding, file transfer or agent forwarding.
Why does my SSH session die when I lock my phone?
The phone pauses apps in the background to save battery, so the client stops answering, and the connection is dropped by the server, the network or the phone itself. A keepalive cannot help while the app is paused; a session that lives on the server, in tmux or a helper, can.