tmux: Sessions That Survive
The SSH guide introduced tmux new, Ctrl+b d to detach, and tmux attach to come back. That is enough to stop losing work. This page covers what comes next: why it works, how to organise more than one job, and how to configure tmux so that reading back through 40,000 lines of aligner output is not painful.
Why Your Job Dies When SSH Drops
Section titled “Why Your Job Dies When SSH Drops”When your connection drops, the remote server notices that your terminal has gone away. It sends a SIGHUP signal, short for “hang up”, to every process attached to that terminal. The default response to SIGHUP is to exit. Your six-hour alignment dies at hour four.
The name is literal. It dates from the era when terminals were connected over telephone lines, and hanging up the phone was how you ended a session.
tmux solves this by owning the terminal itself. Your pipeline is attached to tmux, not to your SSH connection. When SSH drops, tmux is still there on the server, still holding the terminal, still running your job. Nothing gets hung up on.
This is why nohup and & are the weaker answer. They suppress the signal, but you lose the ability to interact with the process, and you are stuck reading a log file to find out what happened. With tmux you reattach and the session is exactly as you left it, cursor and all.
Sessions, Windows, and Panes
Section titled “Sessions, Windows, and Panes”Three levels of nesting, and the distinction matters once you run more than one thing.
A session is the top level. It is what survives a disconnect and what you attach to. Give each project its own.
A window is a full-screen tab inside a session. Ctrl+b c creates one, Ctrl+b n and Ctrl+b p move between them, Ctrl+b 0 through Ctrl+b 9 jump directly.
A pane is a split within a window. Ctrl+b % splits vertically, Ctrl+b " splits horizontally, and Ctrl+b plus an arrow key moves between them. Useful for watching htop beside a running job.
For parallel work, prefer separate sessions over windows in one session. Sessions are the unit tmux lets you attach to independently, so one terminal per session just works. Two windows in one session fight over the same view.
$ tmux new -d -s rnaseq # create detached, do not attach$ tmux new -d -s variants$ tmux lsrnaseq: 1 windows (created Mon Jul 14 09:12:04 2026)variants: 1 windows (created Mon Jul 14 09:12:09 2026)The -d flag is worth knowing. It creates the session without attaching, which is how you start a job and walk away in one command:
$ tmux new -d -s rnaseq "nextflow run nf-core/rnaseq -profile singularity --input samplesheet.csv --outdir results"Scrolling: mouse on Is Not Enough
Section titled “Scrolling: mouse on Is Not Enough”Here is the part that catches everyone.
Attach to a stock tmux and your terminal’s scrollback goes dead. The mouse wheel does nothing. Everything older than the visible screen is unreachable unless you know to press Ctrl+b [.
The reason is the alternate screen. Terminals keep two buffers: the normal one, which accumulates scrollback, and an alternate one, which does not. Full-screen programs like vim, less, and tmux itself switch to the alternate buffer so that your shell history reappears unchanged when they exit. The cost is that while they are running, there is no scrollback for your terminal to scroll. tmux has taken over the screen, so tmux now owns the job of keeping history.
Most advice stops at one line:
set -g mouse onThis is necessary and not sufficient, which is the worst kind of wrong. It makes the wheel work in some contexts and not others rather than failing cleanly, so you conclude tmux is flaky rather than misconfigured.
What mouse on does is let tmux see mouse events. It does not decide what happens with them. For that you need a binding:
bind -n WheelUpPane if-shell -F -t = "#{mouse_any_flag}" \ "send-keys -M" "if -Ft= '#{pane_in_mode}' 'send-keys -M' 'copy-mode -e'"bind -n WheelDownPane select-pane -t= \; send-keys -MThat WheelUpPane binding is a three-way branch, and each branch matters:
#{mouse_any_flag}is true. The program in the pane has asked for mouse events itself, so forward the wheel to it and stay out of the way. This is what letshtopandlesskeep working normally.- Already in copy-mode. Forward the wheel so it keeps scrolling.
- Otherwise. Drop into tmux’s scrollback with
copy-mode -e. The-emeans scrolling back to the bottom exits copy-mode by itself, so you are never stuck in a mode you did not ask to enter.
Then give it some depth:
set -g history-limit 100000The default is 2,000 lines, which is roughly one verbose tool invocation.
What Scrollback Costs
Section titled “What Scrollback Costs”Deep history is not free, and the cost is larger than it looks. Measured on tmux 3.4 at a pane width of 200 columns, with roughly 120 characters of text per line:
| Content | RAM per line |
|---|---|
| Plain text | 3,819 bytes |
| The same text with colour | 3,974 bytes |
About 4 kB of RAM per line, which is some 30 times the size of the text itself. tmux stores a grid of cells rather than a string, at roughly 32 bytes per cell. Colour is not the driver, despite being the obvious suspect. Width is.
history-limit |
RAM per pane |
|---|---|
| 100,000 | ~0.38 GB |
| 500,000 | ~1.9 GB |
| 1,000,000 | ~3.8 GB |
Treat those as an upper bound. Blank and short lines cost proportionally less, because tmux only stores the cells a line actually uses, so a real session lands well under the ceiling.
The number to watch is not the per-pane figure. The tmux server is a single process holding the history of every pane, so your budget is the sum across all sessions. Ten sessions at 100,000 lines is a few gigabytes in one process, and if that process is killed for running out of memory, every detached session dies with it. That is precisely the thing you started using tmux to prevent.
100,000 lines is about 2,000 screenfuls. That covers reading back through a long run without spending headroom on depth you will never scroll to.
A Config to Start From
Section titled “A Config to Start From”Put this in ~/.tmux.conf and reload with tmux source-file ~/.tmux.conf:
# Wheel scrolls tmux's own scrollback rather than sending arrow keys to the app.set -g mouse onbind -n WheelUpPane if-shell -F -t = "#{mouse_any_flag}" \ "send-keys -M" "if -Ft= '#{pane_in_mode}' 'send-keys -M' 'copy-mode -e'"bind -n WheelDownPane select-pane -t= \; send-keys -Msetw -g mode-keys vi
# ~0.38 GB per pane at the top end. Raise with care; the server holds every pane.set -g history-limit 100000
# Truecolor, so syntax highlighting is not mud.set -g default-terminal "tmux-256color"set -ga terminal-overrides ",*256col*:Tc,xterm*:Tc"
# The default 500ms wait after Esc makes vim feel broken rather than slow.set -sg escape-time 10
# Do not shrink a window to fit a stale client left behind by a dropped connection.setw -g aggressive-resize onVerify rather than assume:
$ tmux show-options -gv history-limit100000$ tmux show-options -gv mouseon$ tmux list-keys -T root | grep -c WheelUpPane1One more flag worth adding to your SSH command or ~/.ssh/config:
$ ssh -o ServerAliveInterval=15 -o ServerAliveCountMax=4 labserverWithout keepalives a dropped link does not fail, it hangs. Your client blocks forever on a socket the server has already forgotten, and the terminal keeps accepting input while doing nothing. With them it dies with an error after about a minute, which is what you want, because then you know to reconnect.
Scrollback Over SSH
Section titled “Scrollback Over SSH”A detail that costs people an afternoon. When you are working over SSH, there are two terminals in play: the one you are sitting at, and the one on the server. Scrollback belongs to the machine you are sitting at.
So if you set your terminal’s scrollback to unlimited, do it on your laptop, not on the server you connect to. Setting it on the server does nothing for an SSH session.
Inside tmux this happens not to matter, because tmux keeps its own history and that is what you are scrolling. Outside tmux, on a plain SSH session, your local terminal’s buffer is the whole game. GNOME Terminal defaults to 10,000 lines:
$ P=$(gsettings get org.gnome.Terminal.ProfilesList default | tr -d \")$ gsettings set "org.gnome.Terminal.Legacy.Profile:/org/gnome/terminal/legacy/profiles:/:$P/" scrollback-unlimited trueRunning a Coding Agent Inside tmux
Section titled “Running a Coding Agent Inside tmux”Coding agents like Claude Code are a good fit for tmux. They are long-running, they emit far more than a screenful, and you spend most of your time reading back through what they did.
They also collide with the wheel in a way ordinary programs do not. An agent’s terminal interface may request mouse events itself, which sets mouse_any_flag, which means the first branch of the binding above hands it the wheel. If the agent then converts wheel movement into arrow keys, those arrows land in its prompt box and cycle through your input history. Your 100,000 lines of tmux scrollback sit there, correct and unreachable.
So the tmux config and the agent’s config are load-bearing for each other. Getting tmux right is not enough on its own.
There is a second, quieter version of the same trap. If the agent runs on tmux’s alternate screen, tmux’s history never receives its output at all, for the same reason your terminal’s scrollback goes blind when tmux starts. Measured with 300 lines written before entering the alternate screen and 300 written inside it:
alternate-screen on 302 lines of history (nothing from the alternate screen kept)alternate-screen off 603 lines of history (all of it kept)The depth is real and the content is absent. tmux can refuse the alternate screen outright:
setw alternate-screen off # current window onlyFor Claude Code specifically, the settings that make the wheel behave on Linux are covered in Claude Code on Linux: Scrolling and Copy/Paste.
Quick Reference
Section titled “Quick Reference”| Task | Command |
|---|---|
| New session, attached | tmux new -s name |
| New session, detached | tmux new -d -s name |
| Run a command detached | tmux new -d -s name "command" |
| List sessions | tmux ls |
| Attach | tmux attach -t name |
| Detach | Ctrl+b, then d |
| Kill a session | tmux kill-session -t name |
| New window | Ctrl+b, then c |
| Next / previous window | Ctrl+b, then n / p |
| Split vertically | Ctrl+b, then % |
| Split horizontally | Ctrl+b, then " |
| Move between panes | Ctrl+b, then an arrow key |
| Enter scrollback | Ctrl+b, then [ |
| Leave scrollback | q |
| Reload config | tmux source-file ~/.tmux.conf |
Next Steps
Section titled “Next Steps”tmux keeps the work alive on the machine. Containers keep the environment reproducible, which is the subject of the next section.