tmux: the parts worth knowing
Everything in tmux follows from one fact: it is a client/server program. The tmux command you type is a thin client. A server process holds the sessions, and every pane in every session is a pseudo-terminal that the server owns. Detach, reattach, survive a closed terminal, share a session between two terminals — all of it is just clients coming and going while the server keeps the terminals alive.
The hierarchy is session → window → pane. A session is a project or a context. A window is a full-screen tab inside it. A pane is a split inside a window. Default prefix is Ctrl+b; every shortcut below means: press prefix, release, then the key.
Cheat sheet
The handful that covers a normal day.
tmux new -s dev -c ~/projects/myapp # new named session in a directory
tmux ls # list sessions
tmux a -t dev # attach
tmux a -d -t dev # attach, kick other clients off
tmux kill-session -t dev
tmux kill-server # everything
| Keys | Action |
|---|---|
prefix d | detach — everything keeps running |
prefix s | session picker |
prefix c / prefix , | new window / rename it |
prefix 1 … prefix 9 | jump to window |
prefix l | last window (edit ↔ test bounce) |
prefix % / prefix " | split side by side / top-bottom |
prefix ←↑→↓ | move between panes |
prefix z | zoom pane fullscreen (toggle) |
prefix Space | cycle layouts — fixes a lopsided split |
prefix x | kill pane |
prefix [ | copy mode / scrollback |
prefix : | command prompt |
prefix ? | list every binding |
If you learn only two keys beyond navigation, make them prefix z and prefix l.
Sessions
One session per project or context, not per task. Then prefix s becomes a project switcher.
From the shell, tmux new -s name -c dir creates and attaches. From inside tmux, do not run tmux new -s other — it nests a second client inside the first, and the prefix has to be pressed twice to reach the inner one. Use the command prompt instead:
prefix :
new-session -s infra # create and switch to it
new-session -d -s infra # create in the background, stay put
new-session -s infra -c ~/projects/k8s # with a starting directory
| Keys | Action |
|---|---|
prefix d | detach |
prefix s | session tree — → expands a session to show its windows |
prefix $ | rename session |
prefix ( / prefix ) | previous / next session |
prefix L | last session (capital L, toggles) |
switch-client -t name from the command prompt targets by name; mostly for scripts.
Windows
| Keys | Action |
|---|---|
prefix c | create |
prefix , | rename |
prefix w | interactive picker with previews |
prefix n / prefix p | next / previous |
prefix 1 … prefix 9 | jump by index |
prefix l | last window (toggles) |
prefix & | kill (asks to confirm) |
Rename windows. Three windows called bash give you no navigation cues. Direct numbers cover most switching; prefix l is the fast bounce between two. Closing the last window ends the session; exit in the shell closes a window too.
Panes
| Keys | Action |
|---|---|
prefix % | split side by side (vertical divider) |
prefix " | split top/bottom (horizontal divider) |
prefix ←↑→↓ | navigate |
prefix o | cycle panes |
prefix q | show pane numbers, type one to jump |
prefix z | zoom fullscreen (toggle) |
prefix x | kill pane |
prefix ! | break pane out into its own window |
prefix { / prefix } | swap pane position |
prefix Space | cycle preset layouts |
prefix Alt+1 … Alt+5 | select a layout directly |
The naming is backwards from expectation: % is tmux’s “vertical split” because the divider is vertical.
Resizing
| Keys | Effect |
|---|---|
prefix Ctrl+←↑→↓ | resize by 1 cell |
prefix Alt+←↑→↓ | resize by 5 cells |
Press prefix once, then hold the modifier and tap the arrow repeatedly. Both bindings are declared -r (repeatable), so tmux keeps the prefix active for repeat-time (500ms default); pause longer and you drop out. Direction is relative to the current pane — you push the wall nearest that direction. Left/right resizing between vertically stacked panes does nothing, silently, because no vertical border exists.
Precise sizing via prefix ::
resize-pane -D 10 # down 10 rows
resize-pane -L 20 # left 20 columns
resize-pane -y 15 # set height to exactly 15 rows
resize-pane -x 80 # set width to exactly 80 columns
In practice prefix z and prefix Space replace resizing most of the time: zoom is non-destructive, and Space snaps a broken layout back to something sane.
Copy mode
prefix [ enters, prefix ] pastes, q exits. With mode-keys vi you get h j k l, w/b, Ctrl+u/Ctrl+d, / to search, g/G for top and bottom — a read-only Vim buffer over your scrollback. Default vi-mode selection is Space to start and Enter to copy, not v/y; the config below fixes that.
Config
Where it lives, and the trap
Preferred location since tmux 3.1 (2020) is ~/.config/tmux/tmux.conf. The search order is:
/etc/tmux.conf(system-wide, loaded separately)~/.tmux.conf$XDG_CONFIG_HOME/tmux/tmux.conf~/.config/tmux/tmux.conf
tmux loads the first user config it finds and stops. It does not merge. If ~/.tmux.conf exists at all — even empty — the XDG one is silently ignored. Move it, don’t copy it:
mkdir -p ~/.config/tmux
mv ~/.tmux.conf ~/.config/tmux/tmux.conf
tmux display -p '#{config_files}' # verify which file is live
tmux -V
The config
set -g mouse on
set -g base-index 1
setw -g pane-base-index 1
set -g renumber-windows on
set -g mode-keys vi
set -g history-limit 50000
set -sg escape-time 10
set -g default-terminal "tmux-256color"
set -ga terminal-overrides ",*256col*:Tc,foot*:Tc"
set -g extended-keys on
set -as terminal-features 'foot*:extkeys'
bind r source-file ~/.config/tmux/tmux.conf \; display "config reloaded"
# splits and new windows keep the current directory
bind | split-window -h -c "#{pane_current_path}"
bind - split-window -v -c "#{pane_current_path}"
bind c new-window -c "#{pane_current_path}"
# vim-style selection in copy mode
bind -T copy-mode-vi v send -X begin-selection
bind -T copy-mode-vi y send -X copy-selection-and-cancel
Line by line
mouse on — click to select panes, drag borders to resize, scrollwheel for scrollback. Without it the wheel sends arrow keys to the shell and you scroll through history instead. Tradeoff: mouse selection grabs tmux’s pane content, not the terminal’s; hold Shift while dragging for native selection.
base-index 1 + pane-base-index 1 — default numbering starts at 0, which puts window 0 under the 0 key at the far right of the number row. Starting at 1 aligns numbering with the physical keys. setw is set-window-option; pane indexing is a window-level setting.
renumber-windows on — closing window 2 of 1 2 3 otherwise leaves 1 3, and gaps accumulate.
history-limit 50000 — scrollback per pane. The default is 2000, which one verbose build blows through. Only applies to panes created after the setting loads.
escape-time 10 — the important one. tmux cannot distinguish a bare ESC from the ESC that starts an escape sequence (arrow keys are ESC [ A), so it waits. The default 500ms means a half-second pause every time you leave insert mode in Neovim. 10ms still catches real sequences from a local keyboard and feels instant. -s makes it server-level.
default-terminal "tmux-256color" — what $TERM becomes inside panes. The older screen-256color claims italics aren’t supported, so Neovim silently renders comments without them.
terminal-overrides ... :Tc — truecolor. Without it, Neovim colorschemes get quantized to 256 colors and look muddy.
extended-keys on + terminal-features ':extkeys' — needed for Shift+Enter and other modified keys. Requires tmux 3.5+; on 3.2–3.4 use set -s extended-keys always. Change the foot* glob to match your terminal (xterm-kitty*, wezterm*, xterm-ghostty*); -a appends, so several can be listed. Test with sed -n l and press Shift+Enter: \033[13;2u means it works end to end.
bind r — \; is an escaped semicolon; tmux chains commands with ;, but the config parser would eat a bare one. Caveat: source-file applies new settings but does not undo removed ones. A deleted binding lives in the running server until tmux kill-server. Server-scope options (-s) also need a full restart, not a reload.
#{pane_current_path} — by default a new split inherits the session’s start directory, so splitting three levels deep dumps you back at $HOME. This makes splits open where you are. % and " keep working alongside | and -.
Optional
Vim-style pane navigation shadows the default l = last-window, so decide which you use more:
bind h select-pane -L
bind j select-pane -D
bind k select-pane -U
bind l select-pane -R
Ctrl+a as prefix, inherited from GNU screen. The send-prefix line lets Ctrl+a Ctrl+a send a literal Ctrl+a, preserving readline’s jump-to-start-of-line:
unbind C-b
set -g prefix C-a
bind C-a send-prefix
TPM defaults to ~/.tmux/plugins/. To keep it under XDG, set the path and clone TPM to the matching location — decide before installing plugins, since moving them later means re-cloning:
set -g @plugin 'tmux-plugins/tpm'
run '~/.config/tmux/plugins/tpm/tpm'
Scripted sessions
#!/usr/bin/env bash
tmux new-session -d -s dev -n editor -c ~/projects/myapp
tmux new-window -t dev -n server -c ~/projects/myapp
tmux new-window -t dev -n logs -c ~/projects/myapp
tmux select-window -t dev:editor
tmux attach -t dev
-d creates detached so later commands aren’t fighting an attached client; -n names the window at creation; -c sets its directory; -t dev:editor targets by name (dev:2 by index also works). Drop it in ~/.local/bin/dev-session, chmod +x, and one command rebuilds the layout.
You can launch commands directly — tmux new-window -t dev -n server 'npm run dev' — but the window closes when the command exits, so a crashed process makes a window silently vanish. A plain shell is more forgiving.
Flag placement
tmux [server-flags] command [command-flags]
Server flags (-L, -S, -f, -u, -2) go before the command. Command flags (-s, -c, -n, -d) go after.
tmux -L work new -s app -c ~/projects/app # correct
tmux new -L work -s app # wrong
The wrong form fails with no server running on /tmp/tmux-1000/default — note it says default, not work. The -L work was parsed as a flag to new-session, which has no -L, so the socket name never got set.
-L is not sticky. Every later command needs it too, or it looks at default and finds nothing:
tmux -L work ls
tmux -L work a -t app
tmux -L work kill-session -t app
tmux -L work kill-server # also removes the socket file; ls reporting "no server" afterwards is expected
An alias helps: alias twork='tmux -L work'.
Troubleshooting
no server running on /tmp/tmux-<uid>/<name>— either the server on that socket is gone (start a fresh session), or you put-Lafter the command.sessions should be nested with care— you rantmux newinside tmux. Detach first, or useprefix :new-session.[detached (from session X)]after creating a new session — informational: your terminal was attached to X and switched. Nothing is lost.- A removed binding still works —
source-filedoesn’t undo;tmux kill-serverand start again. - Config changes have no effect —
tmux display -p '#{config_files}'; a stray~/.tmux.confis shadowing the XDG file. - Neovim pauses on ESC —
escape-timeis still at the default; check the setting landed in the live config file. - Colors look wrong in Neovim — missing
Tcoverride or$TERMisscreen-256color.