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, 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 |