为什么你的服务器任务总在 SSH 一断就死
很多站长都有过这样的经历:在 SSH 里敲了一条长命令,比如打包一个几百 MB 的网站备份、跑一次全站图片压缩、或者执行一个几小时的数据库导出,然后网络抖了一下,Putty 弹窗提示连接已断开。等你重新登录上去一看,进程没了,任务只做了一半,甚至留下一个写坏的半成品文件。更糟的是数据库导出做到一半被打断,下次恢复时你都不知道该从哪儿接上。
这个问题不是网络的问题,也不是服务器的错,而是 Linux 的会话与作业控制模型决定的。SSH 登录会打开一个终端会话,这个会话下启动的所有前台进程都挂在会话首进程(session leader)下面。当 SSH 连接断开时,终端驱动会向会话里的所有进程发送 SIGHUP(hangup,挂断)信号,默认行为就是进程直接退出。理解了这个机制,解决方案就清晰了:要么让进程脱离这个会话(nohup、setsid),要么用一个「会话管理器」把进程托管起来,让你随时能重新接回去。本文讲的是后者,也是个人站长最实用的方案:tmux。
三种「让进程活下来」的方案对比
在真正动手之前,先把几个常见方案的差别说清楚,避免你用错工具。
- nohup command &:忽略 SIGHUP,把输出重定向到 nohup.out。优点是一条命令搞定,缺点是无法重新接管交互,只能事后看日志,也没法看到进度条和交互式提示。
- setsid command:让进程独立成一个新会话,脱离当前终端。比 nohup 更彻底,但同样无法重新接管。
- tmux / screen:在服务器上跑一个「会话进程」(server),你连上去只是 attach 到这个会话上,显示的是一块虚拟终端。断开连接时,会话和里面的程序继续跑;下次登录再 attach 回去,屏幕内容原封不动。这是唯一能真正做到「随走随回」的方案。
- systemd service:适合长期常驻的服务,但不适合「临时跑一次、需要看交互输出」的场景。
结论很直接:一次性长任务用 nohup 兜底,需要盯着看或者需要交互的任务用 tmux。而 tmux 相比老牌的 screen,配置更现代、分屏更方便、生态也更活跃,本文以 tmux 为主。
安装与最小可用配置
Debian/Ubuntu 直接装:
apt update
apt install -y tmux
# 确认版本(2.1 以后分屏语法有变化,建议 3.x)
tmux -Vtmux 的默认配置朴素且按键反直觉(前缀键是 Ctrl+b),建议写一份最简配置。注意 tmux 的配置文件有两种写法,新版推荐 ~/.tmux.conf:
# 把前缀键改成 Ctrl+a,更顺手(也避开了和 screen 用户的肌肉记忆冲突)
set -g prefix C-a
unbind C-b
bind C-a send-prefix
# 窗口和面板编号从 1 开始,而不是 0
set -g base-index 1
setw -g pane-base-index 1
set -g renumber-windows on
# 开启鼠标:点击切换面板、拖动调整大小、滚轮翻页
set -g mouse on
# 历史行数,默认只有 2000 行,排查日志时不够用
set -g history-limit 100000
# 用 | 和 - 分屏,符合直觉
bind | split-window -h -c "#{pane_current_path}"
bind - split-window -v -c "#{pane_current_path}"
# 面板间用 vim 风格的 hjkl 切换
bind h select-pane -L
bind j select-pane -D
bind k select-pane -U
bind l select-pane -R
# 关闭时二次确认,防止误杀生产任务
bind x confirm-before -p "kill-pane #P? (y/n)" kill-pane
# 状态栏显示时间、主机名与负载,方便一眼看清环境
set -g status-right "#[fg=green]#(hostname -s) #[fg=white]| %Y-%m-%d %H:%M #[fg=yellow]#(uptime | cut -d ',' -f 3-)"
set -g status-interval 30改完之后在 tmux 里执行 tmux source-file ~/.tmux.conf 立即生效,不用重启会话。
一次完整的实战:跑一个 3 小时的备份任务
假设你要在服务器上跑一次全站打包 + 上传对象存储的备份,预计 2 到 3 小时。正确做法是:
# 1. 建一个命名会话(-s 指定名字,-d 表示后台创建不立刻进入)
tmux new -s backup -d
# 2. 进入这个会话
tmux attach -t backup
# 3. 在里面跑任务,输出同时写到日志文件
cd /www/scripts
./do_backup.sh 2>&1 | tee -a /var/log/backup_$(date +%F).log
# 4. 按 Ctrl+a 然后按 d 脱离(detach)
# 注意:是脱离,不是关闭!任务继续在后台跑
# 5. 断开 SSH,回家吃饭
# 6. 晚上重新登录,接回去
tmux attach -t backup这里有几个关键点必须强调。第一,脱离用 Ctrl+a d,千万不要用 exit 或者 Ctrl+a &(关窗口),后者会真的终止里面的进程。第二,务必用 tee 把输出落盘,原因后面讲坑的时候会说明:tmux 的 scrollback 是存在内存里的,会话一旦重启就全丢。第三,重连时如果提示 sessions should be nested with care,说明你已经在 tmux 里了,先按 Ctrl+a d 出来再 attach。
常用会话管理命令清单
把这些记住,日常运维就够了:
tmux ls # 列出所有会话(等价于 tmux list-sessions)
tmux new -s web -d # 后台新建名为 web 的会话
tmux attach -t web # 进入 web 会话(可简写 tmux a -t web)
tmux a # 只有一个会话时,直接接上
tmux kill-session -t web # 结束整个会话(里面的进程会被杀)
tmux kill-server # 结束所有会话,慎用
tmux rename-session -t web prod # 重命名会话会话内的快捷键(前缀键默认改成 Ctrl+a 后):
Ctrl+a d— 脱离会话(最常用)Ctrl+a c— 新建窗口Ctrl+a n/p— 下一个 / 上一个窗口Ctrl+a 数字— 跳到第 N 个窗口Ctrl+a |/-— 左右 / 上下分屏Ctrl+a z— 当前面板全屏切换(排查报错时非常实用)Ctrl+a [— 进入复制模式,可用方向键和 PageUp 翻历史(配合set -g mouse on可直接滚轮)Ctrl+a ?— 查看全部快捷键
实战场景一:跟随一整个部署流程的日志
做个典型的网站发版:拉代码、装依赖、构建、重启 PHP-FPM。整个过程要看好几分钟的输出,中途断线就前功尽弃。用 tmux 分屏最合适:
tmux new -s deploy
# 左边分屏跑部署脚本
git pull
composer install --no-dev -o
# Ctrl+a | 右边开一个面板,实时看 nginx 和 php-fpm 的错误日志
tail -F /var/log/nginx/error.log
# Ctrl+a o 在两个面板间切换焦点这样一来,部署输出和错误日志在同一个屏幕里并排显示,出问题一眼就能对上时间点。这比开两个 SSH 窗口强得多,尤其是你在手机上用 Termius 之类工具临时处理故障的时候。
实战场景二:多台服务器统一入口
如果你管着两三台机器,可以在本机建一个 tmux 会话,每个窗口对应一台服务器,全部通过 ProxyJump 连过去:
# 本机执行
tmux new -s fleet
# 窗口 1:主站
ssh prod-web-01
# Ctrl+a c 新建窗口 2:数据库
ssh prod-db-01
# Ctrl+a c 新建窗口 3:备份机
ssh backup-01配合 ~/.ssh/config 里的跳板机配置(ProxyJump),一次登录就能在三个窗口之间切换,比反复敲 IP 快得多。需要注意的是:**tmux 会话保存在本机内存里,本机重启就全没了**,但这通常可以接受,因为会话里的 ssh 连接本来也会断。
容易踩的五个坑
坑一:把 tmux 当持久化方案,结果服务器一重启全没了
tmux 的会话只活在内存中。systemctl restart、内核升级重启、或者 OOM 把 tmux server 杀掉,会话和里面的任务都会消失。真正需要「开机自动恢复」的常驻任务,应该交给 systemd service,而不是 tmux。tmux 的定位是临时长任务的会话保持,两者不要混用。
坑二:以为脱离后还能看到历史输出,结果滚不上去
默认 history-limit 是 2000 行,一个跑几小时、每秒钟输出一行的任务很快就刷过去了。所以配置里把 history-limit 调到 100000,同时关键任务一定要用 tee 落盘。这是最省心的组合:屏幕上看着,日志里留着。
坑三:终端尺寸变化导致界面错乱
在手机和电脑之间切换 attach 时,tmux 会按最小尺寸渲染,屏幕上可能出现一堆乱码方块。解决办法有两种:一是当前会话里按 Ctrl+a : 输入 resize-window -A;二是干脆配置里加:
# 允许多客户端各自使用自己的窗口尺寸
setw -g aggressive-resize on
set -g window-size latest坑四:multiplexer 里的时区和 locale 与预期不一致
通过 tmux 跑脚本时,环境变量继承的是创建会话时的那个 shell。如果你在 Windows 的 Git Bash 里创建会话,再在服务器上 attach,可能看到的是奇怪的时间格式。写完脚本先跑一次 echo $LANG; date 确认,必要时在脚本开头显式 export LANG=zh_CN.UTF-8。
坑五:后台会话里的任务失败了你却不知道
脱离之后任务失败了没人告诉你。推荐在脚本末尾加一行失败提示,例如包装一下:
nohup bash -c './do_backup.sh 2>&1 | tee -a /var/log/backup.log; \
echo "exit=$?" >> /var/log/backup.log' &更严谨的做法是接入告警通道——把 echo "BACKUP FAILED" | mail -s ... 或者 webhook 推送写进脚本的 trap 里,这样任务无论成功失败都有回音。
screen 用户迁移提示
如果你原来用 screen,迁移到 tmux 主要注意三点:一是 screen 的会话无法被 tmux 接管,只能共存;二是 screen 的 Ctrl+a d 脱离和 tmux 一样,肌肉记忆可以保留;三是 screen 的分屏是 Ctrl+a S 上下分割,tmux 是 Ctrl+a -。如果只是记不住快捷键,用 tmux list-keys 随时查。
常见问题 FAQ
Q:服务器重启后 tmux 会自动恢复吗?
A:不会。需要额外工具,比如 tmux-resurrect 或 tmux-continuum 插件,但要注意它们恢复的是窗口布局和命令历史,不是进程本身。真正的进程恢复还是得靠 systemd。
Q:脱离(detach)和关闭(kill)怎么区分?
A:detach 是 Ctrl+a d,会话还在服务器上跑;关窗口是 Ctrl+a & 或直接 exit,会终止该窗口内的所有进程。运维时最容易犯的错就是在会话里敲了 exit 然后才发现任务没了。
Q:一个会话能被多人同时 attach 吗?
A:可以,tmux attach -t name 允许多个客户端接同一个会话,屏幕内容同步。但这也意味着任何人都能操作,生产环境要注意服务器账号权限。
Q:怎么把 tmux 的输出录制下来?
A:用 Ctrl+a : 输入 pipe-pane -o 'cat >> ~/tmux-$(date +%F-%H%M).log',之后该面板所有输出都会写入文件,排查长期任务时非常有用。
Q:tmux server 被杀掉之后有日志能查吗?
A:能在系统日志里找线索,journalctl -u tmux 不适用(tmux 一般是用户会话进程),建议查 dmesg | grep -i oom 确认是不是被 OOM Killer 杀的,配合 systemd-coredump 分析。
小结
tmux 不是什么高级工具,但它是个人站长运维体验里性价比最高的一个。掌握三件事就够了:命名会话创建、Ctrl+a d 脱离、attach 接回。剩下的分屏、录屏、状态栏都是锦上添花。真正的纪律是两条:长任务一律放进 tmux 而不是裸跑 SSH;有输出的任务一律 tee 落盘。做到这两点,你就再也不会因为一次网络抖动而重跑三个小时的备份了。