What you're seeing
$ ssh [email protected]
... (you're working, you walk away for lunch, you come back) ...
client_loop: send disconnect: Broken pipe
$Variants:
packet_write_wait: Connection to X port 22: Broken pipeWrite failed: Broken pipeConnection to X closed by remote host.(related, usually a server-side kill rather than NAT drop)
All point at the same underlying thing: a TCP connection that no longer works.
What's causing this error
A long-lived SSH session is a TCP connection that sits idle for minutes at a time. Anything in the path that holds connection state (your home router, a corporate firewall, your VPN, the cloud provider's load balancer) will drop "stale" connections to reclaim memory. Default timeouts are usually 5–15 minutes of total idleness.
When that happens, the next time you press a key, your client tries to write to a socket that's silently dead. The kernel raises EPIPE, which becomes "Broken pipe" in the error output.
The four sources, in rough order of likelihood:
- NAT or firewall idle timeout. Most common. The connection table entry expired.
- Your local network changed. Wi-Fi sleep, switching between Wi-Fi and Ethernet, VPN reconnect. The source IP changed, and the existing TCP socket is now orphaned.
- Server-side
ClientAliveInterval/ClientAliveCountMaxexplicitly disconnects idle sessions. - Network blip. Brief packet loss caused the connection to give up; rare on stable links but common over flaky cellular or distant hops.
How to fix it
Step 1: Add ServerAliveInterval to ~/.ssh/config
This is the fix in 90% of cases. Edit ~/.ssh/config:
Host *
ServerAliveInterval 60
ServerAliveCountMax 3What this does: every 60 seconds of idle time, your client sends a tiny encrypted keepalive to the server. The connection stays "active" from every NAT/firewall's perspective. ServerAliveCountMax 3 means "give up after 3 missed keepalives", so if the connection truly is dead, you find out after ~3 minutes instead of waiting indefinitely.
This applies to every host. If you only want it for specific hosts:
Host work.example.com
ServerAliveInterval 60
ServerAliveCountMax 3Step 2: For sessions you can't afford to lose, use a multiplexer
ServerAliveInterval prevents drops. But for truly long-running work (a multi-hour build, an SSH session you started before lunch), you want the work to survive any disconnect, including ones you can't prevent.
Two options:
# tmux (recommended): start it on the server, detach safely.
$ ssh user@server
$ tmux new -s work
# Work happens. If the SSH session drops, the tmux session keeps running.
# Reconnect and re-attach:
$ ssh user@server
$ tmux attach -t workAlternative: mosh, a UDP-based SSH replacement that survives IP changes and reconnects automatically. Worth it if you frequently switch networks.
Step 3: Server-side fix (only if you control the server)
If users keep getting kicked, you can tell sshd to send keepalives from its side:
# /etc/ssh/sshd_config
ClientAliveInterval 60
ClientAliveCountMax 3Restart sshd:
sudo systemctl restart sshNow the server keeps the connection warm even for clients that don't set their own keepalive.
Common edge cases
| Situation | What's actually wrong |
|---|---|
| Drops at exactly 5 / 10 / 15 minutes idle | NAT or firewall timeout. Fix with ServerAliveInterval |
| Drops every time you switch Wi-Fi networks | Source IP changed; TCP can't recover. Use mosh or accept it. |
| Drops only on corporate Wi-Fi | Corporate firewall has very aggressive idle timeouts. ServerAliveInterval 30 (more aggressive) usually solves it |
| Drops randomly mid-typing | Genuine network instability. Try mosh, or check if your link has packet loss (mtr <host>) |
Connection to X closed by remote host instead | Server-side disconnect. Check sshd_config ClientAliveInterval on the server, or session policy that's killing idle users |
| Keepalive enabled but still drops over VPN | VPN MTU mismatch. Keepalives are tiny so they pass, but real traffic fragments. Lower MTU with Tunnel/TCPKeepAlive directives |
| Stays connected, but commands hang | Server is alive but a process inside the session is stuck (network call timing out). Not the same problem, ignore broken pipe advice |