返回博客

nohup 骗了我:Ubuntu 22.04 的 logind 会在 SSH 退出时杀掉所有后台进程

我需要远程改一台服务器的静态 IP。因为 netplan apply 一执行 SSH 就会断(IP 变了),所以我套了 nohup + & + disown,打算让命令在后台跑完,自己主动 exit

理论天衣无缝。结果 IP 没变,netplan apply 压根没跑完。

现象

nohup sudo netplan apply >/tmp/netplan.log 2>&1 &
disown
exit
# SSH 重连后——/tmp/netplan.log 空的,IP 没变

前台直接跑 sudo netplan apply 立刻生效,换成 nohup 版本就失效。

诊断指纹:

journalctl -t systemd-logind | tail -20
# 输出含:Killing user process ... (PID xxxxx)
# 或:Removing user root runtime directory

看到 Killing user process,案子就破了。

根因

Ubuntu 22.04 起,systemd-logind 默认开启 KillUserProcesses=yes。含义:用户最后一个 login session 结束时,logind 会向该用户的所有进程发 SIGTERM,紧接着 SIGKILL

坑:nohup 只防 SIGHUP,不防 SIGTERM/SIGKILL
  • nohup:屏蔽 SIGHUP,对 SIGTERM/SIGKILL 无效
  • disown:把 job 从 shell 的 jobs 表移除,但不解开 cgroup 归属
  • setsid:在某些 logind 版本下仍被 cgroup 杀掉

logind session 结束时走的是 user.slice cgroup 批量杀,不管你 fork 了几层、disown 了没有——只要进程还挂在那个 user slice 下,一律收走。Ubuntu 22.04 的 /etc/systemd/logind.conf 注释掉了这个选项,但代码里的默认值就是 yes,等于开箱即开。

正解

把命令托管给 systemd 的一次性 transient unit,彻底脱离 user session 的生命周期:

sudo systemd-run --no-block --unit=my-task /path/to/cmd arg1 arg2
  • --no-block:systemd-run 立刻返回,不等 unit 结束
  • --unit=<name>:给 unit 起名,方便事后查状态和日志

查看执行结果:

systemctl status my-task.service
journalctl -u my-task --no-pager
正解:用 systemd-run 把命令挂到 system.slice,脱离 user session

远程改 IP 的完整安全操作(含 5 分钟自动回滚保险绳):

# 第一步:安排 300 秒后自动回滚(先跑,确保 systemd 里已登记)
sudo systemd-run --on-active=300 --unit=ip-rollback bash -c "
  cp /tmp/netplan.bak /etc/netplan/50-cloud-init.yaml
  rm -f /etc/netplan/01-static.yaml
  /usr/sbin/netplan apply
"
 
# 第二步:立即 apply 新 IP(SSH 会断,但 rollback timer 已在 systemd 里)
sudo systemd-run --no-block --unit=netplan-apply-now /usr/sbin/netplan apply

从新 IP 重新 SSH 进来,如果新 IP 通了就取消回滚:

sudo systemctl stop ip-rollback.timer ip-rollback.service 2>/dev/null
sudo systemctl reset-failed ip-rollback.{timer,service} 2>/dev/null

排查速查

# 确认本机是否受影响
loginctl show-user $USER | grep KillUserProcesses
# KillUserProcesses=yes → 受影响
 
# 查看 logind 日志
journalctl -t systemd-logind | grep -i kill | tail -20
临时逃生舱(不推荐长期用)

如果实在改不了调用方式,可以编辑 /etc/systemd/logind.conf,添加:

KillUserProcesses=no

然后 sudo systemctl restart systemd-logind强烈不建议在多用户或生产机器上这么做——会让所有用户的后台进程跨 session 无限存活,是资源治理和安全的倒退。

一句话外卖

Ubuntu 22.04+ 上,任何需要在 SSH 退出后继续跑的命令,都应该用 systemd-run --no-block 托管,而不是 nohup