tmux会话管理:断线重连与长任务保活

一、先说清 tmux 与 screen 的分工

SSH 会话断开时,前台进程会收到 SIGHUP。结果就是:一条跑了四十分钟的 mysqldump 断在一半,一个正在编译的项目从头再来,一次 apt full-upgrade 卡在「是否保留配置文件」的提问上再也没人回答。这个问题 1980 年代就有了,解法到 2026 年依然是终端复用器:screen(1987)和 tmux(2007)。

两者都能做到「断线后进程继续跑、重连后看到之前的输出」,但取舍不同:

维度screentmux
安装情况几乎每台老机器都有Debian 11 起默认没装
配置语法古老的类 termcap 写法清晰的命令式配置
分屏与窗口基本完善,支持 layout 脚本化
会话共享支持支持,且权限模型更清楚
脚本自动化弱send-keys、capture-pane 可编排

结论:新机器一律装 tmux,老机器上的 screen 不用急着换。两者的会话不会互相看见,别指望在 screen 里开的东西能用 tmux 接回去——这是第一次用时最常见的困惑。

二、装好后先改三处默认配置

tmux 的默认前缀键是 Ctrl+b,与很多编辑器的「光标左移」冲突(emacs 习惯的人尤其难受),默认滚动是 Ctrl+b [,也不够顺手。装完先写一份最小配置:

apt install -y tmux
cat > ~/.tmux.conf <<'CONF'
# 前缀改成 Ctrl+a(screen 用户习惯),也可以保留 Ctrl+b
set -g prefix C-a
unbind C-b
bind C-a send-prefix

# 鼠标:滚轮翻页、点选窗格、拖动调整大小
set -g mouse on

# 历史行数:默认只有 2000,看日志完全不够
set -g history-limit 50000

# 窗口与面板编号从 1 开始,而不是 0
set -g base-index 1
setw -g pane-base-index 1

# 新窗口/新面板继承当前路径
bind c new-window -c "#{pane_current_path}"
bind '"' split-window -c "#{pane_current_path}"
bind % split-window -h -c "#{pane_current_path}"

# 关掉状态栏的毫秒级刷新,省一点 CPU
set -g status-interval 5
CONF
tmux source-file ~/.tmux.conf

history-limit 是我认为最该改的一项。默认 2000 行,查一段 Nginx 日志或者一次 apt upgrade 的输出,翻到一半就滚不动了。mouse on 在服务器上有个副作用:选中文本会变成 tmux 的复制模式,本机终端剪贴板不好使,所以下面还会讲怎么处理复制。

三、会话、窗口、面板:三层结构一次说透

tmux 的结构是 会话(session)> 窗口(window)> 面板(pane)。断线重连保住的是会话,窗口和面板都在会话里面,所以「保住进程」实际上就是「进程跑在一个没被杀掉的会话里」。

日常最常用的几条命令:

tmux new -s ops            # 新建名为 ops 的会话
tmux ls                    # 列出所有会话
tmux attach -t ops         # 接入已有会话
tmux a                     # 只有一个会话时的简写
tmux kill-session -t ops   # 干掉会话(里面的进程会一起死)
tmux rename-session -t 0 ops   # 重命名

会话一定要起名字。默认名字是数字 0、1、2,开三个之后完全分不清哪个是「在跑备份」、哪个是「在改 Nginx」。命名规则我习惯用「主机-用途」:web1-backup、db1-upgrade,半年后回来看日志一眼就知道是什么。

窗口层面的常用快捷键(前缀键统一写作 P,即 Ctrl+a):

  • P c 新建窗口,P , 重命名窗口,P & 关闭窗口
  • P 0~9 直接跳到对应编号窗口,P n / P p 前后切换
  • P w 列出所有窗口交互式选择,窗口多的时候这个最快
  • P % 左右分屏,P " 上下分屏,P 方向键 切换面板,P z 当前面板全屏/还原

P z 值得单独说:在一个小面板里看长日志,按 P z 临时放大到整屏,看完再按一次缩回去,比来回切窗口快得多。

四、断线之后怎么接回来

这是所有 tmux 用法的核心。SSH 断线后再连上,执行 tmux attach 就回到了原来的画面。但如果忘了自己在哪个会话里,先 tmux ls:

ssh root@server
tmux ls

看到 ops: 3 windows (created Sat Oct 31 02:14:07 2026) 这样的输出,说明会话还活着,tmux attach -t ops 就能接回去。如果报 no sessions,说明会话已经没了——常见原因是服务器重启,或者有人在会话里按了 exit 把最后一个 shell 关掉。

其中一个特别有用的技巧是分离而不是关闭:在会话里按 P d(detach),进程继续跑、会话保留、你回到普通 shell。很多人的习惯是直接关终端窗口,这在 SSH 场景下等于发 SIGHUP,反而把会话里的前台进程杀掉。养成「先 detach 再关窗口」的习惯,能省下大量重跑的时间。

还有一条容易忽略的:tmux 只保护会话里跑的进程。如果进程是 tmux new -s x "cmd" 起的,脱离 tmux 时它会跟着 tmux 服务端存活;但如果进程自己 daemon 化或者被 systemd 接管,那本来就不需要 tmux。反过来说,长期任务不该只依赖 tmux——服务器重启就没了。长期任务请用 systemd 服务或 nohup + setsid 组合,tmux 负责的是「交互式长任务」。

五、复制粘贴:最容易让人放弃 tmux 的地方

开了 mouse on 之后,用鼠标拖选文本,选中的内容会直接进 tmux 的缓冲区,而不是系统剪贴板。远程场景下没法直接访问本机剪贴板,于是很多人以为「tmux 里不能复制」。

三种解法,按场景选:

解法一:按住 Shift 拖选。大多数终端(iTerm2、Windows Terminal、GNOME Terminal)在按住 Shift 时会跳过终端的鼠标捕获,直接走本地选择。这是最快的临时办法。

解法二:用 tmux 自己的复制模式。按 P [ 进入复制模式(开了 mouse 之后直接用滚轮就进去了),用方向键或 / 搜索定位,按空格开始选择、回车结束,内容进 tmux 缓冲区,然后 P ] 粘贴。缓冲区里的内容可以用 tmux save-buffer /tmp/clip.txt 存成文件再 scp 回本地。

解法三:让 tmux 直接调系统剪贴板。如果服务器上装了 xclip 且通过 SSH X11 转发,可以配置 set -g set-clipboard on 并配合终端支持 OSC 52,这样复制会写进本机剪贴板。但 X11 转发在很多 VPS 上没配,OSC 52 也不是所有终端都支持,实际成功率不高,我一般不折腾这个。

反向的粘贴(从本机粘进 tmux)也要注意:如果开了 mouse on,右键菜单的「粘贴」可能被 tmux 吃掉,用 P ] 从缓冲区粘贴更可靠。

六、会话共享与多人协作的注意事项

tmux 天然支持「两个 SSH 连接接同一个会话」,这在排查线上问题时非常好用——一个人操作,另一个人在边上实时看。做法就是两个人用同一个系统账号 tmux attach -t ops,画面完全同步。

但这也意味着风险:共享会话里的操作是不可分辨的。日志里看到「谁删了文件」只能查到账号,查不到具体是哪个人按的。所以生产环境我一般只允许临时共享,问题排查完立刻 tmux kill-session。

如果是不同账号需要共享,tmux 支持基于 socket 权限的共享:在配置文件里或启动时指定 tmux -S /tmp/shared.sock -f ~/.tmux.conf new -s shared,再给 socket 加上组权限。这个配置比较繁琐,个人站长的场景里基本用不上,知道有这回事就行。

七、几个真实踩过的坑

坑一:会话里的终端尺寸错乱。大屏上开的会话,从小屏终端接回来,界面会错位、换行乱掉。原因是 tmux 记住了最大尺寸。解决:tmux attach -d -t ops 时先 detach 掉其他客户端,或者在配置里加 setw -g aggressive-resize on 让它按当前客户端尺寸渲染。

坑二:history-limit 只在新建窗口时生效。改完配置 source-file 之后,老窗口的历史还是 2000 行。需要 tmux kill-server 重启服务端才会全部生效,或者手动 tmux new-window 开新窗口。

坑三:镜像里的 tmux 版本太老。CentOS 7 自带 1.8,不支持 #{pane_current_path} 这样的格式字符串,配置加载会报错。先 tmux -V 看一眼,低于 2.6 就别用新语法。

坑四:把 tmux kill-server 敲在了生产机。这个命令会杀掉所有会话和其中的所有进程,没有任何确认提示。我现在的做法永远先 tmux ls 看清单,再 kill-session -t 名字 逐个关,绝不用 kill-server。

最后一点经验:tmux 的价值不在于「炫技般的分屏」,而在于它把「任务运行」和「连接状态」解耦。一旦养成「所有超过五分钟的命令都放到命名会话里跑」的习惯,断线、笔记本合盖、Wi-Fi 切换都不再是需要重来一次的事故。

八、把 tmux 做成开机自启的常驻工作台

顺手之后,很多人会想「一登录就自动进到那个会话里」。这个需求在 tmux 3.x 上有现成做法,在更老的版本上要用条件判断绕过。

# ~/.bashrc 末尾
if [ -z "$TMUX" ] && [ -n "$SSH_TTY" ]; then
    tmux attach -t ops 2>/dev/null || tmux new -s ops
fi

这段逻辑要说清三点。第一,必须先判断 $TMUX 为空,否则在会话里再开一个 shell 会无限嵌套,典型表现是「窗口标题越来越长、按键延迟逐渐变大」。第二,判断 $SSH_TTY 非空,避免本机控制台登录也跳进 tmux——本地机器上的 tmux 会干扰图形终端。第三,attach 失败再 new,用 || 串联,是因为会话不存在时 attach 会返回非零,正好用来兜底。

如果想让会话在机器重启后自动恢复,那就不能靠 bashrc 了。tmux 本身在进程被杀后不会保留任何东西,所有「恢复」方案都是「在会话里跑一个记录脚本,重启后再按记录重建」。tmux-resurrect 和 tmux-continuum 两个插件做这件事,但对个人站长来说,更简单的替代是把真正的长期任务交给 systemd,tmux 只负责交互。别为了「重启后窗口还在」去装一堆插件,那层复杂度很少值得。

反过来,有一类场景确实需要常驻会话:给团队留一个「运维窗口」,谁都可能进去看一眼日志。这时推荐用独立 socket 加指定配置,避免个人配置干扰:

tmux -S /tmp/ops.sock -f /etc/tmux.conf new -s ops -d
chgrp opsadmins /tmp/ops.sock && chmod 660 /tmp/ops.sock

-d 表示后台创建不立即接入,这样可以在系统启动脚本里安全执行。socket 的组权限决定了谁能接入,比共享系统账号可控得多。

九、用命令行截取面板输出:把 tmux 当轻量监控

tmux 还有一个几乎没人用的能力:capture-pane 可以把某个面板的当前内容导出为文本。这意味着在会话里跑一个持续滚动的监控命令(比如 tail -f 或者 top),然后从外面用一条命令抓取最新画面,不需要登录进去看。

tmux capture-pane -p -t ops:1 -S -200 > /tmp/ops_pane.txt
tail -20 /tmp/ops_pane.txt

-p 输出到标准输出,-t 指定「会话:窗口」,-S -200 表示从历史里往回取 200 行。把它放进一个定时任务,每小时抓一次,就能在没有任何监控系统的情况下,得到一份「这台机器上跑的操作在做什么」的记录。对于只跑了一两个自建服务的 VPS,这个成本比装 Prometheus 低得多。

# crontab -e
17 * * * * /usr/bin/tmux capture-pane -p -t ops:0 -S -500 >> /var/log/tmux_snapshot.log 2>&1

配合 logrotate 限制这个文件的体积,就形成了一个几乎零成本的审计尾巴。有一个细节:capture-pane 需要以会话所有者身份执行,root 的 cron 抓不到普通用户的会话,所以要写进对应用户自己的 crontab。

Last modification:October 31st, 2026 at 01:24 pm

Leave a Comment