A dropped SSH connection should never kill a database migration, a long rsync, or a kernel build. Tmux keeps your shells alive on the server independently of the network, and it can be driven entirely from scripts, which makes it far more than a manual convenience. This guide covers the operational patterns that matter: detached sessions for batch jobs, programmatic session creation, sane scrollback on a small VPS, and the configuration that keeps sessions alive across reboots.
The core model: server, session, window, pane
One tmux server runs per user and holds many sessions. Each session owns windows, and each window can be split into panes. Because the server is a daemon, everything inside survives your SSH client disconnecting. The moment you understand this hierarchy, scripting becomes obvious.
# start a named, detached session running a job
tmux new-session -d -s dump 'mysqldump --single-transaction app | gzip > /backup/app.sql.gz'
# list sessions
tmux ls
# reattach later
tmux attach -t dump
# wait for the job to finish from a script
while tmux has-session -t dump 2>/dev/null; do sleep 5; done
echo "dump finished"
Preserving exit codes from a tmux job
A detached command’s exit status vanishes when the session closes. Write it to a file so your automation can branch on it:
tmux new-session -d -s deploy \
'cd /srv/app && git pull && docker compose up -d --build; echo $? > /tmp/deploy.exit'
tmux wait-for deploy 2>/dev/null || true
cat /tmp/deploy.exit # 0 means success
This pattern is the building block for any “run long job, keep transcript” workflow, and it pairs naturally with a properly provisioned VPS where you control the process lifetime directly.
Scripted multi-pane layouts
You can rebuild a full monitoring layout with a handful of commands — useful for a morning triage session.
tmux new-session -d -s ops -n watch
# top half: resource overview
tmux send-keys -t ops:watch.0 'vmstat 2' C-m
# split horizontally, run connection tracking below
tmux split-window -h -t ops:watch
tmux send-keys -t ops:watch.1 'watch -n2 "ss -s; echo; nstat -az | tail -5"' C-m
# second window for logs
tmux new-window -t ops -n logs
tmux send-keys -t ops:logs 'journalctl -f -u nginx' C-m
tmux select-window -t ops:watch
tmux attach -t ops
Scrollback and memory on a small VPS
The default 2000-line scrollback is easy to exhaust during a verbose build. Raising it costs memory per pane — roughly history lines multiplied by pane width in bytes. On a 1 GB instance keep it moderate and rely on logging to disk for anything you must keep.
# ~/.tmux.conf
set -g history-limit 5000
set -g mouse on
set -g base-index 1
setw -g pane-base-index 1
set -g renumber-windows on
set -sg escape-time 10
set -g status-interval 15
| Setting | Default | Suggested | Effect |
|---|---|---|---|
| history-limit | 2000 | 5000 | scrollback depth |
| escape-time | 500 ms | 10 ms | vim latency over SSH |
| mouse | off | on | scroll/select panes |
| base-index | 0 | 1 | keyboard ergonomics |
Surviving reboots without systemd
Tmux sessions die when the machine reboots, which is usually correct — but for a persistent console on a headless box, a systemd unit that recreates a session at boot is the clean answer.
# /etc/systemd/system/tmux-console.service
[Unit]
Description=Persistent tmux console
After=network.target
[Service]
Type=forking
User=admin
ExecStart=/usr/bin/tmux new-session -d -s console
ExecStop=/usr/bin/tmux kill-session -t console
[Install]
WantedBy=multi-user.target
Common failure modes
- “sessions should be nested with care” — you are inside tmux already; use
tmux lsandtmux attachinstead ofnew-session, or prefix withTMUX= tmux .... - Session vanishes on logout — a lingering SSH session ran
exitin the last window, which destroys the session. Enableset -g destroy-unattached off. - Output garbled after reconnect — resize mismatch; run
tmux attach -dto force-detach the stale client, then reattach. - Scripts hang on attach — always use
-din automation; never attach from a non-interactive shell.
Copy-mode, piping and scripting basics
Tmux exposes its own command language on the shell, which is what makes it scriptable. The same commands you type with the prefix key can be issued as tmux <command>.
# capture the visible pane, or the full scrollback with -S
# -S - means start from the very top of the history
tmux capture-pane -t ops:watch.1 -p
# save the last 5000 lines of a build to a file
tmux capture-pane -t build -S -5000 -p > /tmp/build.log
# pipe a pane's output to a file live
tmux pipe-pane -t ops:logs -o 'cat >> /tmp/nginx-$$.log'
# send text into a pane from a script
tmux send-keys -t ops:watch.0 'systemctl status nginx' C-m
# rename a window so your layout is self-documenting
tmux rename-window -t ops:watch 'resources'
capture-pane plus pipe-pane are the two commands that turn tmux from a convenience into infrastructure: you can drive a long job, stream its log to disk, and retrieve the transcript without ever attaching interactively.
| Task | Command |
|---|---|
| Create detached session | tmux new-session -d -s name |
| Run command and keep session | tmux new-session -d -s n 'cmd' |
| Detach a client | tmux detach-client |
| Kill one session | tmux kill-session -t name |
| Kill everything | tmux kill-server |
| Snapshot a pane | tmux capture-pane -p |
These primitives compose: a wrapper script can create a session, send a sequence of commands, wait on has-session, then read the captured output — giving you an auditable, replayable execution model on any server you reach over SSH. That is why tmux pairings appear in deployment runbooks alongside a properly configured service manager, and why stripping it out in favour of raw background processes usually costs more than it saves.
Summary
Tmux is a process supervisor you already have installed. Use detached, named sessions for batch work, persist exit codes to files, script layouts for triage, and hand long-lived consoles to systemd. Combined with the process and memory controls described in the tutorials section, it turns a single small VPS into a dependable workload host.




Leave a Reply
You must be logged in to post a comment.