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 Hartupdated Aug 10, 20268 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. Both halves are ordinary tmux commands with all their usual flags, documented in the tmux man page; this line only depends on their exit statuses. All of it assumes tmux is actually installed on the far end โ€” see installing tmux, or tmux on Windows if the box you're SSHing into is running WSL2.

local โ€” attach-or-create
ssh build-box -t 'tmux attach || tmux new'
# session existed: dropped straight into it
ssh build-box -t 'tmux attach || tmux new'
# first connection ever: new session created instead

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. That is the single most common complaint in tmux from your phone, and it is not phone-specific at all โ€” any small client does it. 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 โ€” both keywords below are standard OpenSSH client options, described in ssh_config(5):

~/.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, and it is the problem tmux in the browser exists to survey. 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 over an end-to-end encrypted tunnel โ€” no SSH client and no key on the device doing the checking, even though the session it's serving still runs exactly as described above. The feature list says what it does; the sealed tunnel docs say how the transport works. It still won't tell you when the build ends โ€” you open the session and read it, same as tmux attach.

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