For years my relationship with long-running terminal work was a slow-motion accident waiting to happen. I'd kick off a migration, or a build, or a rsync of something enormous, over SSH from my laptop. Then a train would go into a tunnel, or I'd shut the lid out of habit, or the wifi would hiccup, and the SSH connection would die and take the shell, and the job, with it. Each time I'd swear I'd sort it out properly. Each time I'd forget until the next tunnel.
I have finally sorted it out properly. The short version: tmux on the remote, a handful of config changes, two habits, and a plugin for surviving reboots. I have not lost work to a dropped connection in over a year, and the setup is boring enough that I trust it. Boring is the goal.
the core idea, in case it's new to you
tmux is a terminal multiplexer. The bit that matters here is the multiplexing of sessions, not the panes everyone shows off in screenshots. When you run tmux on a remote host, the shells live inside the tmux server process on that host. Your SSH connection is just a window onto it. Kill the connection and the server keeps running, blissfully unaware, with your job still chugging. Reconnect, run tmux attach, and you're back exactly where you were.
That's the whole trick, and it's why screen and tmux have outlived a hundred shinier tools. The work is detached from the connection.
my actual config
I keep ~/.tmux.conf deliberately small. Every line earns its place, because a config you don't understand is a config you can't fix at 2am on a box you've SSH'd into in a panic.
# sane prefix; Ctrl-b is a stretch, Ctrl-a is muscle memory from screen
set -g prefix C-a
unbind C-b
bind C-a send-prefix
# don't make me wait for the escape key (vim feels broken otherwise)
set -sg escape-time 10
# huge scrollback; I want the error that scrolled off ten minutes ago
set -g history-limit 50000
# tell me where I am; the status bar earns its rent
set -g status-left-length 30
set -g status-left "#[bold]#S #[default]"
set -g status-right "#H %d %b %H:%M"
# renumber windows when one closes, so the list stays tidy
set -g renumber-windows on
# mouse on, because sometimes I just want to scroll
set -g mouse on
# 256 colour and true colour so nothing looks washed out
set -g default-terminal "tmux-256color"
set -ga terminal-overrides ",*256col*:Tc"
Nothing exotic. The escape-time line is the one people forget and then quietly hate vim over SSH for years without realising why. The 50,000 lines of scrollback have rescued me more times than any clever pane layout.
the two habits that matter more than the config
Config is the easy part. The habits are what actually keep the work safe.
The first habit: named sessions, always. I never start work on a remote box with a bare tmux. I start with tmux new -s migrate or tmux new -s deploy-api. When I reconnect, tmux ls shows me a list of meaningful names instead of 0, 1, 2, and I attach to the right one without guessing. On a host where three different bits of work might be running, this is the difference between calm and chaos.
The second habit: attach, don't create. Reconnecting, I run tmux attach -t migrate, not tmux new. It sounds obvious until the day you absent-mindedly start a fresh session on top of an existing one, see an empty shell, assume your work vanished, and have a small heart attack before tmux ls reminds you it's all still there. I now have a shell function that does the right thing:
# attach to the named session or create it if it doesn't exist
ta() { tmux attach -t "$1" 2>/dev/null || tmux new -s "$1"; }
So ta deploy either drops me back into my deploy session or starts one. One verb, no thinking, no accidental duplicates.
surviving an actual reboot
Detaching survives a dropped connection for free, because the server keeps running. It does not survive the remote host rebooting, because then the tmux server dies with everything else. For a long time I told myself that was fine, since a reboot kills the running job anyway. Mostly true. But the layout of a session, which windows, what was in each, where I was, is real context I'd built up, and losing all of it to a kernel update was annoying.
So I run two plugins via tpm, the tmux plugin manager: tmux-resurrect and tmux-continuum.
# at the bottom of ~/.tmux.conf
set -g @plugin 'tmux-plugins/tpm'
set -g @plugin 'tmux-plugins/tmux-resurrect'
set -g @plugin 'tmux-plugins/tmux-continuum'
set -g @continuum-restore 'on'
set -g @continuum-save-interval '15'
run '~/.tmux/plugins/tpm/tpm'
resurrect snapshots the session layout to disk on demand. continuum automates it: it saves every fifteen minutes and, with @continuum-restore on, restores the last snapshot when tmux next starts. So after a reboot I attach and my windows are laid out the way I left them. The processes don't come back, of course, nothing magic is happening, but the structure does, and that's most of what I actually wanted.
A caveat worth stating plainly: resurrect restores panes and layouts, and optionally running programs if you opt in, but it cannot resurrect arbitrary in-flight state. Don't mistake it for a process checkpointer. It brings back the room, not the conversation that was happening in it.
the honest summary
This is not clever. There is no trick here that a thousand people haven't written up before. The reason I'm bothering to write it down is that I knew all of this for years and still kept losing work, because knowing tmux exists is not the same as having the habits wired in. The config took twenty minutes. The habits took longer, and they're the bit that actually pays off.
The next time the train goes into a tunnel, the job keeps running on the server, and I reconnect on the other side and carry on. The first time that happened after I'd set it all up, I genuinely grinned at my phone like an idiot. Small thing. Enormously satisfying.