Skip to content

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.

Ivo Hart7 min read

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.

local โ€” attach-or-create
[object Object],[object Object],[object Object],[object Object]

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.

FixHow
Different prefix per machineSet the local prefix to Ctrl-a and leave the remote at the default Ctrl-b โ€” no collision, no extra keystrokes
Press the prefix twicetmux'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 keyIn 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:

~/.ssh/config
Host *
    ServerAliveInterval 60
    ServerAliveCountMax 3

ServerAliveInterval 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 SSHmosh
TransportTCPUDP
Survives a changed IP (wifi to cellular)No โ€” the TCP connection itself breaksYes โ€” mosh re-establishes the UDP session automatically
Local echo while the network is slowNo โ€” waits on a round tripYes โ€” predicts and displays keystrokes locally
Session persists if the client machine rebootsYes, because the session lives on the serverOnly if paired with tmux โ€” mosh itself doesn't multiplex or persist
SetupNothing โ€” SSH and tmux, both already installed most placesRequires 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.

Related reading

mobile

tmux from your phone: the modifier key problem

A native SSH app, a web terminal, or a purpose-built client can reach tmux from a phone โ€” the real obstacle is Ctrl-b on a screen with no keyboard.

Ivo Hart7 min read