tmux
tmux ssh: fixing broken pipe and dropped sessions
tmux over SSH keeps a job running through a dropped connection. The pattern is ssh host -t 'tmux attach || tmux new', plus keepalives for broken pipe.
tmux over SSH is the standard fix for a connection that drops mid-job: start or attach to a tmux
session on the remote machine, and whatever you're running keeps going on the server even after
the SSH connection itself dies. The one-liner is ssh host -t 'tmux attach || tmux new' โ attach
if a session exists, create one if it doesn't โ and the -t matters, covered below.
Why a dropped connection doesn't kill your job
A program started at a plain SSH shell prompt is a child of that shell. When the connection
drops โ laptop sleeps, network blips, you close the terminal โ the shell and its pseudo-terminal
go away, and SIGHUP propagates down to whatever it was running. That's why a long npm run build started over a bare SSH session dies the moment the connection does.
Inside tmux, the program you started is a child of the tmux server, a background process on
the remote machine that has nothing to do with your SSH connection. Losing the connection just
detaches you โ the same as pressing prefix d on purpose. The job keeps running; you just aren't
watching it.
The attach-or-create pattern
ssh host -t 'tmux attach || tmux new'tmux attach succeeds if any session exists and fails otherwise, so || tmux new only runs when
there was nothing to attach to. Run this and you either land back in whatever you had running, or
get a fresh session โ never an error about a missing session name.
The -t is not optional here. SSH only allocates a pseudo-terminal automatically when you don't
pass a remote command; the moment you hand it 'tmux attach || tmux new', you have to force it
with -t or tmux fails with open terminal failed: not a terminal. If you're just running ssh host with no command and attaching manually afterward, you don't need it โ the terminal is
already there.
The nested-tmux problem
If you already run tmux locally โ inside iTerm2, or as your default shell wrapper โ and then SSH
into a box that also runs tmux, both are listening for the same prefix key. Press
Ctrl-b and your local tmux catches it first; the remote session never sees it,
which makes it look like none of your remote key bindings work.
| Fix | How |
|---|---|
| Different prefix per machine | Set the local prefix to Ctrl-a and leave the remote at the default Ctrl-b โ no collision, no extra keystrokes |
| Press the prefix twice | tmux's default bindings already forward it: prefix then C-b sends a literal C-b through to whatever's running, including a nested tmux |
| A dedicated forwarding key | In the outer config, bind an unused key with -n to send-prefix โ for example bind-key -n C-g send-prefix โ so it reaches the inner session without touching your normal prefix |
Different prefixes per machine is the one worth actually adopting if you SSH into tmux sessions regularly โ it's zero extra keystrokes forever, versus remembering to double-tap every time. Nothing about the rest of tmux changes once you're attached โ tmux commands covers the full session, window and pane reference for whatever you do next.
"Client too small" and the resize fight
Attaching from a narrow terminal โ a phone SSH client, a small split โ into a session that's
already attached elsewhere can shrink everyone's view to fit yours, because by default a tmux
window sizes itself to the smallest attached client. tmux attach session
covers the full mechanics of window-size and aggressive-resize; the SSH-specific fix is
simpler: attach with -d to detach every other client first instead of sharing the view.
ssh host -t 'tmux attach -d || tmux new'That's the safer default for a quick SSH check-in from a small screen โ you get your own view at your own size, and nobody else's layout moves because of it.
SSH keepalives: the real fix for broken pipe
Write failed: Broken pipe almost never means tmux crashed โ it means the underlying TCP
connection died silently, usually because a NAT gateway or firewall dropped an idle connection
without telling either end. tmux itself is unaffected; the session is still running on the
server, waiting for you to reconnect.
The fix is a client-side keepalive, in ~/.ssh/config:
Host *
ServerAliveInterval 60
ServerAliveCountMax 3ServerAliveInterval 60 sends a null packet every 60 seconds if nothing else is happening,
which keeps the connection looking "active" to anything in between that would otherwise time out
an idle one. ServerAliveCountMax 3 is how many of those can go unanswered before SSH gives up
and disconnects on its own โ better than hanging indefinitely on a connection that's actually
dead.
tmux + SSH vs mosh
| tmux over SSH | mosh | |
|---|---|---|
| Transport | TCP | UDP |
| Survives a changed IP (wifi to cellular) | No โ the TCP connection itself breaks | Yes โ mosh re-establishes the UDP session automatically |
| Local echo while the network is slow | No โ waits on a round trip | Yes โ predicts and displays keystrokes locally |
| Session persists if the client machine reboots | Yes, because the session lives on the server | Only if paired with tmux โ mosh itself doesn't multiplex or persist |
| Setup | Nothing โ SSH and tmux, both already installed most places | Requires mosh installed on both ends and a UDP port range open |
These solve different layers and are often used together rather than as alternatives: mosh replaces the fragile TCP transport with something that survives roaming and packet loss, but it doesn't give you sessions, panes, or anything that outlives the mosh client itself โ that's still tmux's job. If your problem is a laptop that changes networks constantly, mosh plus tmux beats either alone. If your problem is just an occasional dropped connection on a stable network, keepalives and tmux are enough.
Checking whether a long SSH-and-tmux job actually finished is the same "not at my desk" problem
either way. If reattaching over SSH every time you want a status check gets old, mtmux
(npm i -g mtmux) serves the same tmux session to a browser tab and can push a notification when
a pane goes quiet โ no SSH client required for the check itself, even though the session it's
watching still runs exactly as described above.
How do I attach to a tmux session over SSH?
ssh host -t 'tmux attach || tmux new' attaches if a session already exists and creates one
otherwise. The -t forces a pseudo-terminal, required because you're passing SSH a remote
command.
What causes 'broken pipe' in tmux over SSH?
The underlying TCP connection dying silently โ usually a NAT or firewall timing out an idle
connection โ not tmux itself. The session keeps running on the remote server; reconnect and
reattach. ServerAliveInterval in ~/.ssh/config prevents most cases by keeping the connection
active.
Why doesn't my tmux prefix key work after SSHing into a remote tmux?
You're running tmux locally too, and your local session is catching the prefix before it reaches the remote one. Use a different prefix per machine, or press the prefix twice โ tmux's default bindings forward a doubled prefix to whatever's running underneath, including a nested tmux.
Should I use mosh instead of tmux over SSH?
They solve different problems. mosh replaces SSH's TCP transport with something that survives network changes and packet loss; tmux gives you a persistent, multiplexed session. Most people who need both use mosh to connect and tmux inside it, rather than picking one.
Why does my tmux session shrink when I attach from my phone over SSH?
By default a tmux window sizes itself to the smallest attached client. Attach with tmux attach -d to detach other clients first instead of sharing the view, or see
tmux attach session for the aggressive-resize fix if you want
multiple clients to stay attached at different sizes.
Put this to work โ mtmux attaches to the tmux server you already run.