tmux & screen - Street-Level Ops¶
Real-world workflows for using terminal multiplexers in production operations.
Long-Running Operations in tmux¶
The number one rule: never run a long operation over bare SSH. Always wrap it in tmux first.
# Before starting a database migration
tmux new-session -s db-migration
# Now run the migration inside tmux
pg_dump production | psql staging
# Before a kernel upgrade
tmux new-session -s kernel-upgrade
sudo apt update && sudo apt upgrade -y linux-image-generic && sudo reboot
# Before a large file transfer
tmux new-session -s data-sync
rsync -avz --progress /data/ remote:/backup/data/
If your SSH drops, reconnect and reattach:
Multi-Pane Monitoring Dashboard¶
Build a monitoring layout for incident response:
#!/bin/bash
# monitoring-layout.sh — 4-pane monitoring dashboard
SESSION="monitor"
tmux new-session -d -s "$SESSION" -x 200 -y 50
# Pane 0: system metrics
tmux send-keys -t "$SESSION" "htop" Enter
# Pane 1: application logs (right side)
tmux split-window -h -t "$SESSION"
tmux send-keys -t "$SESSION" "journalctl -u myapp -f" Enter
# Pane 2: network connections (bottom left)
tmux split-window -v -t "$SESSION:0.0"
tmux send-keys -t "$SESSION:0.2" "watch -n 2 'ss -tunapl | head -30'" Enter
# Pane 3: disk I/O (bottom right)
tmux split-window -v -t "$SESSION:0.1"
tmux send-keys -t "$SESSION:0.3" "iostat -xz 2" Enter
# Set even layout
tmux select-layout -t "$SESSION" tiled
# Attach
tmux attach -t "$SESSION"
Result:
┌───────────────────┬────────────────────┐
│ htop │ journalctl -f │
│ │ │
├───────────────────┼────────────────────┤
│ ss -tunapl │ iostat -xz 2 │
│ │ │
└───────────────────┴────────────────────┘
Kubernetes Incident Response Layout¶
#!/bin/bash
# k8s-incident.sh — k8s debugging layout
SESSION="k8s-incident"
NS="${1:-default}"
tmux new-session -d -s "$SESSION"
# Pane 0: pod status (auto-refresh)
tmux send-keys "watch -n 5 'kubectl get pods -n $NS -o wide'" Enter
# Pane 1: events stream
tmux split-window -h -t "$SESSION"
tmux send-keys "kubectl get events -n $NS --watch" Enter
# Pane 2: logs from problem pod (manual — fill in pod name)
tmux split-window -v -t "$SESSION:0.0"
tmux send-keys "# kubectl logs -f <pod-name> -n $NS" Enter
# Pane 3: interactive shell for kubectl commands
tmux split-window -v -t "$SESSION:0.1"
tmux send-keys "kubectl config current-context && echo '---' && kubectl get nodes" Enter
tmux select-layout -t "$SESSION" tiled
tmux attach -t "$SESSION"
Shared Sessions for Pair Debugging¶
Two engineers can attach to the same tmux session and see identical output in real time:
# Engineer 1 (on shared server)
tmux new-session -s incident-42
# Engineer 2 (SSHs to the same server)
tmux attach -t incident-42
# Both engineers now see and can type in the same session
# Ctrl-b : to enter command mode and type:
# setw synchronize-panes on
# Now keystrokes go to ALL panes simultaneously (useful for multi-server ops)
For read-only viewing (one engineer watches, the other drives):
Automating Layouts with tmuxinator¶
tmuxinator defines reusable session layouts in YAML:
# Install
gem install tmuxinator
# or
pip install tmuxp # Python alternative
# Create a project
tmuxinator new ops-dashboard
# ~/.config/tmuxinator/ops-dashboard.yml
name: ops-dashboard
root: ~/
windows:
- monitoring:
layout: tiled
panes:
- htop
- journalctl -u nginx -f
- watch -n 5 'df -h | grep -v tmpfs'
- tail -f /var/log/syslog
- shell:
panes:
- # Empty pane for commands
- logs:
layout: even-horizontal
panes:
- tail -f /var/log/nginx/access.log
- tail -f /var/log/nginx/error.log
# Launch the layout
tmuxinator start ops-dashboard
# Stop (kills the session)
tmuxinator stop ops-dashboard
# List available layouts
tmuxinator list
Capturing Output for Postmortems¶
During an incident, capture everything happening in your panes:
# Start logging a pane to a file (do this FIRST when incident starts)
tmux pipe-pane -t incident-42:0.0 "cat >> ~/postmortem/pane0-$(date +%Y%m%d-%H%M%S).log"
tmux pipe-pane -t incident-42:0.1 "cat >> ~/postmortem/pane1-$(date +%Y%m%d-%H%M%S).log"
# Capture current visible content + scrollback
tmux capture-pane -t incident-42:0.0 -p -S - > ~/postmortem/scrollback-pane0.txt
# Stop logging when incident is over
tmux pipe-pane -t incident-42:0.0 ""
Script it for all panes:
#!/bin/bash
# capture-all-panes.sh — capture every pane in a session
SESSION="$1"
OUTDIR="$HOME/postmortem/$(date +%Y%m%d-%H%M%S)"
mkdir -p "$OUTDIR"
for pane in $(tmux list-panes -s -t "$SESSION" -F '#{window_index}.#{pane_index}'); do
tmux capture-pane -t "$SESSION:$pane" -p -S - > "$OUTDIR/pane-${pane}.txt"
done
echo "Captured to $OUTDIR"
ls -la "$OUTDIR"
tmux + SSH for Serial Console Access¶
When managing bare-metal servers or network gear, tmux keeps your serial console alive:
# Start tmux before connecting to serial console via SSH jump
tmux new-session -s console-sw01
# Connect through jump host to serial console
ssh -J jumphost serialconsole@172.16.0.1
# If SSH to jump host drops, tmux holds the session
# Reconnect: tmux attach -t console-sw01
For multiple console connections:
tmux new-session -s consoles
# Window per device
tmux send-keys "ssh -J jump serial@switch-01" Enter
tmux new-window -t consoles
tmux send-keys "ssh -J jump serial@switch-02" Enter
tmux new-window -t consoles
tmux send-keys "ssh -J jump serial@router-01" Enter
# Navigate: Ctrl-b 0, Ctrl-b 1, Ctrl-b 2
Recovering Orphaned tmux Sessions¶
After a server reboot or crash, check for surviving sessions:
# List all tmux sessions
tmux ls
# If tmux server is dead but socket file exists
ls -la /tmp/tmux-$(id -u)/
# If you get "no server running on /tmp/tmux-1000/default"
# The server died — sessions are gone. This is why tmux-resurrect matters.
# Check if another user has tmux sessions (as root)
ls -la /tmp/tmux-*/
# Force-kill a stuck tmux server
tmux kill-server
# Reconnect to a session that lost its client
tmux attach -d -t infra
# -d detaches any other clients first
Gotcha: After a server reboot, tmux sessions do not survive -- they are held in memory, not on disk. If you need session persistence across reboots, install
tmux-resurrectortmux-continuum. Without these plugins, the only thing that survives a reboot is your session layout YAML files (tmuxinator/tmuxp). ```text
Scripted Pane Layouts for Common Workflows¶
Deploy Watcher¶
```bash
!/bin/bash¶
deploy-watch.sh — monitor a deployment across multiple signals¶
APP="${1:?Usage: deploy-watch.sh
tmux new-session -d -s "$SESSION"
Top-left: deployment status¶
tmux send-keys "watch -n 3 'kubectl rollout status deployment/$APP 2>&1'" Enter
Top-right: pod status¶
tmux split-window -h tmux send-keys "watch -n 3 'kubectl get pods -l app=$APP -o wide'" Enter
Bottom-left: application logs¶
tmux split-window -v -t 0.0 tmux send-keys "kubectl logs -f deployment/$APP --tail=50 --all-containers" Enter
Bottom-right: events¶
tmux split-window -v -t 0.1 tmux send-keys "kubectl get events --field-selector involvedObject.name=$APP --watch" Enter
tmux select-layout tiled tmux attach -t "$SESSION" ```text
Multi-Server Command Execution¶
```bash
!/bin/bash¶
multi-server.sh — open panes to multiple servers, optionally synchronized¶
SERVERS=("web-01" "web-02" "web-03" "db-01") SESSION="multi"
tmux new-session -d -s "$SESSION" "ssh ${SERVERS[0]}"
for server in "${SERVERS[@]:1}"; do tmux split-window -t "$SESSION" "ssh $server" tmux select-layout -t "$SESSION" tiled done
Uncomment to synchronize input across all panes:¶
tmux setw -t "$SESSION" synchronize-panes on¶
tmux attach -t "$SESSION" ```text
With synchronized panes, every keystroke goes to every server simultaneously — useful for verifying config changes across a fleet, but dangerous if you are not careful about what you type.
War story: An engineer had synchronize-panes enabled across 12 production servers and typed
rm -rf /tmp/deploy-*in what they thought was a single pane. It ran on all 12 simultaneously. Fortunately it was just tmp files -- but the habit of running destructive commands with sync on has ended careers. Always toggle sync off (Ctrl-b : setw synchronize-panes off) before running anything you would not want on every server.
screen Emergency Workflows¶
When tmux is not available:
```bash
Start a named screen session for the operation¶
screen -S upgrade
Run your long operation¶
apt upgrade -y
Detach: Ctrl-a d¶
Reconnect from another SSH session¶
screen -r upgrade
If screen says "attached" elsewhere, force takeover¶
screen -dr upgrade
-d detaches the other client, -r reattaches to you¶
List all screen sessions¶
screen -ls
There are screens on:¶
12345.upgrade (Detached)¶
12346.monitor (Attached)¶
Kill a dead screen session¶
screen -X -S 12345.upgrade quit ```text