tmux keeps terminal sessions alive across SSH disconnects, splits work across multiple panes, and enables fully scriptable terminal automation. This skill covers session management, window/pane layout, keybinding patterns, and using tmux non-interactively from shell scripts — essential for remote servers, long-running jobs, and automated workflows.
tmux commands from bash scripts without user interactiontmux has three hierarchy levels: sessions (top level, survives disconnects), windows (tabs within a session), and panes (splits within a window). Everything is controllable from outside via tmux <command> or from inside via the prefix key (Ctrl-b by default).
# Create a new named session
tmux new-session -s work
# Create detached (background) session
tmux new-session -d -s work
# Create detached session and start a command
tmux new-session -d -s build -x 220 -y 50 "make all"
# Attach to a session
tmux attach -t work
tmux attach # attaches to most recent session
# List all sessions
tmux list-sessions
tmux ls
# Detach from inside tmux
# Prefix + d (Ctrl-b d)
# Kill a session
tmux kill-session -t work
# Kill all sessions except the current one
tmux kill-session -a
# Rename a session from outside
tmux rename-session -t old-name new-name
# Switch to another session from outside
tmux switch-client -t other-session
# Check if a session exists (useful in scripts)
tmux has-session -t work 2>/dev/null && echo "exists"
# Create a new window in the current session
tmux new-window -t work -n "logs"
# Create a window running a specific command
tmux new-window -t work:3 -n "server" "python -m http.server 8080"
# List windows
tmux list-windows -t work
# Select (switch to) a window
tmux select-window -t work:logs
tmux select-window -t work:2 # by index
# Rename a window
tmux rename-window -t work:2 "editor"
# Kill a window
tmux kill-window -t work:logs
# Move window to a new index
tmux move-window -s work:3 -t work:1
# From inside tmux:
# Prefix + c — new window
# Prefix + , — rename window
# Prefix + & — kill window
# Prefix + n/p — next/previous window
# Prefix + 0-9 — switch to window by number
# Split pane vertically (left/right)
tmux split-window -h -t work:1
# Split pane horizontally (top/bottom)
tmux split-window -v -t work:1
# Split and run a command
tmux split-window -h -t work:1 "tail -f /var/log/syslog"
# Select a pane by index
tmux select-pane -t work:1.0
# Resize panes
tmux resize-pane -t work:1.0 -R 20 # expand right by 20 cols
tmux resize-pane -t work:1.0 -D 10 # shrink down by 10 rows
tmux resize-pane -Z # toggle zoom (fullscreen)
# Swap panes
tmux swap-pane -s work:1.0 -t work:1.1
# Kill a pane
tmux kill-pane -t work:1.1
# From inside tmux:
# Prefix + % — split vertical
# Prefix + " — split horizontal
# Prefix + arrow — navigate panes
# Prefix + z — zoom/unzoom current pane
# Prefix + x — kill pane
# Prefix + {/} — swap pane with previous/next
# Send a command to a specific pane and press Enter
tmux send-keys -t work:1.0 "ls -la" Enter
# Run a command in a background pane without attaching
tmux send-keys -t work:editor "vim src/main.py" Enter
# Send Ctrl+C to stop a running process
tmux send-keys -t work:1.0 C-c
# Send text without pressing Enter (useful for pre-filling prompts)
tmux send-keys -t work:1.0 "git commit -m '"
# Clear a pane
tmux send-keys -t work:1.0 "clear" Enter
# Check what's in a pane (capture its output)
tmux capture-pane -t work:1.0 -p
tmux capture-pane -t work:1.0 -p | grep "ERROR"
This is the most powerful pattern: create a fully configured multi-pane workspace from a single script.
#!/usr/bin/env bash
set -euo pipefail
SESSION="dev"
# Bail if session already exists
tmux has-session -t "$SESSION" 2>/dev/null && {
echo "Session $SESSION already exists. Attaching..."
tmux attach -t "$SESSION"
exit 0
}
# Create session with first window
tmux new-session -d -s "$SESSION" -n "editor" -x 220 -y 50
# Window 1: editor + test runner side by side
tmux send-keys -t "$SESSION:editor" "vim ." Enter
tmux split-window -h -t "$SESSION:editor"
tmux send-keys -t "$SESSION:editor.1" "npm test -- --watch" Enter
tmux select-pane -t "$SESSION:editor.0"
# Window 2: server logs
tmux new-window -t "$SESSION" -n "server"
tmux send-keys -t "$SESSION:server" "docker compose up" Enter
tmux split-window -v -t "$SESSION:server"
tmux send-keys -t "$SESSION:server.1" "tail -f logs/app.log" Enter
# Window 3: general shell
tmux new-window -t "$SESSION" -n "shell"
# Focus first window
tmux select-window -t "$SESSION:editor"
# Attach
tmux attach -t "$SESSION"
~/.tmux.conf)# Change prefix to Ctrl-a (screen-style)
unbind C-b
set -g prefix C-a
bind C-a send-prefix
# Enable mouse support
set -g mouse on
# Start window/pane numbering at 1
set -g base-index 1
setw -g pane-base-index 1
# Renumber windows when one is closed
set -g renumber-windows on
# Increase scrollback buffer
set -g history-limit 50000
# Use vi keys in copy mode
setw -g mode-keys vi
# Faster key repetition
set -s escape-time 0
# Reload config without restarting
bind r source-file ~/.tmux.conf \; display "Config reloaded"
# Intuitive splits: | and -
bind | split-window -h -c "#{pane_current_path}"
bind - split-window -v -c "#{pane_current_path}"
# New windows open in current directory
bind c new-window -c "#{pane_current_path}"
# Status bar
set -g status-right "#{session_name} | %H:%M %d-%b"
set -g status-interval 5
# Enter copy mode (scroll up through output)
# Prefix + [
# In vi mode:
# / to search forward, ? to search backward
# Space to start selection, Enter to copy
# q to exit copy mode
# Paste the most recent buffer
# Prefix + ]
# List paste buffers
tmux list-buffers
# Show the most recent buffer
tmux show-buffer
# Save buffer to a file
tmux save-buffer /tmp/tmux-output.txt
# Load a file into a buffer
tmux load-buffer /tmp/data.txt
# Pipe pane output to a command
tmux pipe-pane -t work:1.0 "cat >> ~/session.log"
# Idempotent session: create or attach
ensure_session() {
local name="$1"
tmux has-session -t "$name" 2>/dev/null \
|| tmux new-session -d -s "$name"
tmux attach -t "$name"
}
# Run a command in a new background window and tail its output
run_bg() {
local session="${1:-main}" cmd="${*:2}"
tmux new-window -t "$session" -n "bg-$$"
tmux send-keys -t "$session:bg-$$" "$cmd" Enter
}
# Wait for a pane to produce specific output (polling)
wait_for_output() {
local target="$1" pattern="$2" timeout="${3:-30}"
local elapsed=0
while (( elapsed < timeout )); do
tmux capture-pane -t "$target" -p | grep -q "$pattern" && return 0
sleep 1
(( elapsed++ ))
done
return 1
}
# Kill all background windows matching a name prefix
kill_bg_windows() {
local session="$1" prefix="${2:-bg-}"
tmux list-windows -t "$session" -F "#W" \
| grep "^${prefix}" \
| while read -r win; do
tmux kill-window -t "${session}:${win}"
done
}
# SSH and immediately attach to an existing session
ssh user@host -t "tmux attach -t work || tmux new-session -s work"
# Run a command on remote host inside a tmux session (fire and forget)
ssh user@host "tmux new-session -d -s deploy 'bash /opt/deploy.sh'"
# Watch the remote session output from another terminal
ssh user@host -t "tmux attach -t deploy -r" # read-only attach
# Pair programming: share a session (both users attach to the same session)
# User 1:
tmux new-session -s shared
# User 2 (same server):
tmux attach -t shared
-s name) in scripts — unnamed sessions are hard to target reliablytmux has-session -t name 2>/dev/null before creating to make scripts idempotent-x and -y when creating detached sessions to give panes a proper size for commands that check terminal dimensionssend-keys ... Enter for automation rather than piping stdin — it works even when the target pane is running an interactive program~/.tmux.conf in version control for reproducibility across machinesbind -n for bindings that don't need the prefix, but only for keys that don't conflict with application shortcutssend-keys executes commands in a pane without confirmation — verify the target (-t session:window.pane) before use in scripts to avoid sending keystrokes to the wrong pane-r) is appropriate when sharing sessions with others to prevent accidental inputProblem: tmux commands from a script fail with "no server running"
Solution: Start the server first with tmux start-server, or create a detached session before running other commands.
Problem: Pane size is 0x0 when creating a detached session
Solution: Pass explicit dimensions: tmux new-session -d -s name -x 200 -y 50.
Problem: send-keys types the text but doesn't run the command
Solution: Ensure you pass Enter (capital E) as a second argument: tmux send-keys -t target "cmd" Enter.
Problem: Script creates a duplicate session each run
Solution: Guard with tmux has-session -t name 2>/dev/null || tmux new-session -d -s name.
Problem: Copy-mode selection doesn't work as expected
Solution: Confirm mode-keys vi or mode-keys emacs is set to match your preference in ~/.tmux.conf.
@bash-pro — Writing the shell scripts that orchestrate tmux sessions@bash-linux — General Linux terminal patterns used inside tmux panes@ssh — Combining tmux with SSH for persistent remote workflows