Workflows: SSH, Nesting, Editor Integration, Pair Programming
SSH and Reconnection
The classic tmux use case: you're on a remote box, the connection drops, you want to pick up where you left off.
On the remote:
ssh you@server
tmux new -A -s work # attach to or create session "work"
# ... do long-running work
Connection drops. No panic. Reconnect:
ssh you@server
tmux a -t work
Everything's still there. Running processes still running. Scrollback intact.
Auto-attach on SSH
Put this in your remote shell's rc file (~/.bashrc or ~/.zshrc):
if [ -n "$SSH_CONNECTION" ] && [ -z "$TMUX" ]; then
tmux new -A -s main
fi
Every SSH login drops you straight into a tmux session named "main". Some people love this; some find it annoying when they want a quick throwaway shell. If you want an escape hatch:
if [ -n "$SSH_CONNECTION" ] && [ -z "$TMUX" ] && [ "$1" != "plain" ]; then
tmux new -A -s main
fi
Nested Sessions: The Prefix Problem
Run tmux inside tmux (common when SSHing from a tmux pane on your laptop to a tmux session on a server). Both sessions listen for the same prefix. Your prefix press goes to the outer session, never reaching the inner one.
Three ways to cope:
1. Press prefix twice
<prefix> <prefix> key
The first prefix gets consumed by the outer; the second (sent via send-prefix) reaches the inner. This works but is clumsy.
2. Use a different prefix on remote
On the remote server's .tmux.conf:
set -g prefix C-s
bind C-s send-prefix
Now your laptop session uses Ctrl-a and the remote session uses Ctrl-s. No conflict.
3. Toggle outer-prefix mode
A popular pattern: bind a key on your laptop to "pass prefix through for a while":
bind -T root F12 set prefix None \; set key-table off \; if -F '#{pane_in_mode}' 'send-keys -X cancel'
bind -T off F12 set -u prefix \; set -u key-table \; refresh-client -S
Press F12 to disable your laptop's prefix entirely, so every key passes to the remote. Press F12 again to re-enable. Not for everyone, but if you spend a lot of time nested, worth learning.
Editor Integration
vim and tmux side by side
Open vim in one pane, a shell in another. Use <prefix> o or vim-style pane nav (if configured) to switch. Run tests in the shell pane, see results, back to editor.
vim-tmux-navigator
The seamless version. With the plugin in both vim and tmux:
Ctrl-h move one pane left (works whether you're in vim or a shell)
Ctrl-j down
Ctrl-k up
Ctrl-l right
No prefix. The same key navigates between vim splits or between tmux panes as appropriate. Once you have this, you stop thinking about the split boundary at all.
Sending commands from vim to a tmux pane
The vim-slime plugin (jpalardy/vim-slime) sends text from vim to a REPL in another tmux pane. Great for iterating on SQL, Python, or Elixir:
# in vim, select a region and press C-c C-c
Text goes to whichever pane you configured. Works with Jupyter, iex, psql, whatever.
Pair Programming Over a Shared Session
Two users on the same machine, sharing a tmux session.
Same user, two clients
Trivial. Both people attach:
tmux a -t pair
Both see the same panes. Both cursors move; whichever keyboard types wins.
Different users, same machine
Create a shared socket:
tmux -S /tmp/pair new -s pair
chmod 777 /tmp/pair
chgrp developers /tmp/pair # optional: group-restrict
The other user attaches:
tmux -S /tmp/pair a -t pair
Over SSH
Both people SSH to the same box, one creates the session, the other attaches. Free, works everywhere, no extra tools.
For more polished pairing, tmate forks tmux and adds peer-to-peer sharing with tokenised URLs. Same keybindings as tmux; takes a few minutes to set up.
Session Templates per Project
Chapter 9 covered scripted sessions. The workflow:
~/bin/start-api # one command starts the session
The script either creates the layout or attaches to the existing session. Over time you accumulate one script per project. A shared bin/ directory in your dotfiles keeps them versioned.
For declarative templates, tmuxp / tmuxinator (chapter 9) let you describe a session in YAML. Either is a good step up from shell scripts if your templates get long.
Multiple Monitors
You have two external monitors, each showing a different terminal window, each attached to the same tmux session. Shared cursor, shared scrolling. Not always useful, but occasionally exactly right:
# terminal 1
tmux a -t work
# terminal 2 (different window or monitor)
tmux a -t work
Both clients follow the same active window. When you want independent views, use two sessions instead.
Different windows on different monitors
Group sessions let you share windows but keep cursors independent:
tmux new -s work
tmux new -s work-b -t work # linked; separate active window
Now work and work-b share the pool of windows. Switch work to window 0 on monitor 1, work-b to window 2 on monitor 2, and you see both at once.
Niche. Works well when it fits.
Persistent Background Tasks
Long-running commands that aren't quite daemons:
tmux new -d -s watch 'tail -F /var/log/app.log | grep ERROR'
Starts a detached tmux session running tail | grep. Check in any time with tmux a -t watch. Kill with tmux kill-session -t watch.
Useful for ad-hoc monitoring that you don't want to run under systemd.
Incident Response
Your service is on fire. You need to watch logs, SSH to three servers, check metrics, and take notes, all at once.
tmux new -s incident
<prefix> c -n notes # notes window
<prefix> c -n logs # logs
<prefix> c -n servers # servers
<prefix> , servers # focus servers window
<prefix> " # split
<prefix> " # split again
# one pane per server
<prefix> :setw synchronize-panes on # broadcast commands to all three
ssh prod-1 # sends to each pane; connects to prod-1, prod-2, prod-3
Sync-panes lets you run tail -f /var/log/app.log on all three servers with one keystroke. Turn it off when you want to interact with one server individually.
After the incident, detach. The session keeps running. Come back tomorrow to review scrollback and write the postmortem.
Quick Dev Environment in a Script
Typical start-dev for a web project:
#!/usr/bin/env bash
set -euo pipefail
cd "$HOME/code/app"
S=app
if tmux has-session -t "$S" 2>/dev/null; then
exec tmux a -t "$S"
fi
tmux new -d -s "$S"
tmux rename-window -t "$S:1" editor
tmux send-keys -t "$S:editor" "vim ." Enter
tmux new-window -t "$S" -n server
tmux send-keys -t "$S:server" "npm run dev" Enter
tmux new-window -t "$S" -n shell
tmux select-window -t "$S:editor"
exec tmux a -t "$S"
Put in $PATH. One command, full environment. Over time this script grows with your project.
When tmux Meets VS Code and Friends
A common objection: "I use VS Code; why do I need tmux?"
You probably don't, for the editor. Splits, file navigation, and terminal tabs are all in VS Code. tmux still earns its keep for:
- SSH work. VS Code Remote is great until it isn't. tmux works over any shell
- Server administration. No GUI, no VS Code. Your Linux box has bash and tmux
- Background sessions. Tasks that live beyond your editor
- Pair programming across different editors
If your work is 100% inside a GUI editor, you can skip tmux. If any part of it involves SSH or long-lived shells, tmux repays the learning fast.
Common Pitfalls
"Prefix key collides with my shell." Ctrl-a conflicts with bash/emacs beginning-of-line, for instance. Trade-off: rebind tmux, or remember <prefix> a for the literal sequence.
"Nested sessions are driving me mad." Use a different prefix remotely, or adopt the F12 toggle trick.
"I SSHed out and my tmux session on the laptop disappeared." Your terminal likely timed out. The session survives. Open a new terminal, tmux a, back to it.
"I shared the socket with chmod 777 and now I'm nervous about security." Reasonable. Restrict by group or use tmate for explicit sharing. 777 on a shared machine is not ideal.
"I want tmux only for some SSH targets." Instead of auto-starting in .bashrc, define a shell function: dev() { ssh "$1" -t 'tmux new -A -s main' }. Use dev server when you want tmux; plain ssh server for a quick shell.
Next Steps
Continue to 12-best-practices.md for the habits that separate fluent tmux users from survivors.