别再 nohup 了:systemd 管理服务的正确姿势

别再 nohup 了:systemd 管理服务的正确姿势

systemdUbuntuLinux服务管理
📶 入门 🧩 systemd · Ubuntu

在 Ubuntu 24.04 上跑 Node 后端或 Go 服务时,很多人习惯顺手敲 nohup ./app &。也有人把进程扔进 Screen 或 Tmux 窗口里。但服务器上的常驻服务必须交给 systemd 管,而不是依赖后台挂起命令。

因为只有 unit 文件能同时解决「进程死了自动拉起、开机自启、结构化日志存储」这三件事。用户级会话工具只能保证当前不掉线。把管理权交给系统底层的 init 进程,才是运维的正轨。

写 unit 文件:用声明式配置接管进程

在终端里敲后台命令,进程状态全靠脑记。在 /etc/systemd/system/ 目录下创建一个独立的 .service 文件,能把启动逻辑用标准文本固化下来。用声明式配置替代命令式脚本,是现代服务管理的基石。

用 nano 创建一个名为 myapp.service 的文件,填入基础骨架:

# /etc/systemd/system/myapp.service
[Unit]
Description=My Custom App Service
After=network.target

[Service]
Type=simple
User=www-data
WorkingDirectory=/var/www/myapp
ExecStart=/usr/bin/node server.js
Restart=always
RestartSec=3

[Install]
WantedBy=multi-user.target

上述配置分为三个核心区块:

  • [Unit] 区块定义服务的基本信息与启动依赖。这里的 After=network.target 要求系统在网络组件就绪后再启动该服务。这能避免程序找不到网卡报错。
  • [Service] 区块定义进程的执行方式。User=www-data 指定了运行权限。WorkingDirectory 指定工作目录。ExecStart 是具体的启动命令,必须使用绝对路径。
  • [Install] 区块定义服务在什么系统状态下激活。

注入环境变量:让配置脱离代码

真实服务通常需要连接数据库。敏感信息写死在代码中容易引发安全事故。在 unit 文件里可以直接声明环境变量。或者指定一个外部的 .env 文件。环境配置与代码执行强制分离,应用部署才能标准化。

编辑刚才的 [Service] 区块,加入环境变量声明:

[Service]
# ... 保持其他配置不变
Environment="NODE_ENV=production"
Environment="DB_HOST=127.0.0.1"

# 当环境变量较多时,推荐使用单独的配置文件
EnvironmentFile=/etc/myapp/config.env

写好配置文件后,Node 进程内可直接通过 process.env.DB_HOST 读到参数。这样便无需修改业务代码。

配置自动拉起:进程崩溃后 3 秒复活

服务程序遇到未捕获异常时会直接退出。如果没有守护进程,服务就断联了。把进程生死权交给底层的 systemd,比通过 Crontab 定时跑检查脚本可靠。这与在容器里跑应用(参考 Docker 部署 Node 应用)时的重启策略作用相同。

在上面的配置文件中,Restart=always 强制服务在退出时立刻重启。RestartSec=3 则让系统主动等待 3 秒再去拉起。缓冲时间避免了程序启动即崩溃引发的 CPU 满载死循环。

每次新建或修改了 /etc/systemd/system/ 目录下的配置,都必须通知 systemd 重新加载缓存:

sudo systemctl daemon-reload
sudo systemctl start myapp.service

验证进程是否成功运行,直接查看状态:

sudo systemctl status myapp.service

绑定系统启动链:设定服务开机自启

服务器重启后,未设置自启的服务需要手动敲命令恢复。进行安全加固(参考 服务器安全加固)或内核更新后,必然面临这个问题。真正的常驻,意味着它必须无缝融入系统的启动链条。

执行开启自启命令。systemd 会在 /etc/systemd/system/multi-user.target.wants/ 目录下创建一个软链接,指向你的原始配置文件。这准确对应了配置中 WantedBy=multi-user.target 这行声明。

sudo systemctl enable myapp.service

查验自启配置是否挂载成功:

sudo systemctl is-enabled myapp.service

抛弃 nohup.out:用 journalctl 看日志

nohup 默认把输出全塞进当前目录的 nohup.out 文本里。这文件没有轮转机制,早晚会把磁盘撑爆。systemd 默认接管进程的终端输出。它会自动打上高精度时间戳,存入 journal 数据库。这种机制从根本上解决了日志堆积的麻烦。

通过 journalctl 工具可以随时调阅结构化日志。加上 -f 参数能实时滚动:

sudo journalctl -u myapp.service -f

利用它还可以精准切割时间段,迅速定位线上事故:

# 只看今天早上的报错日志
sudo journalctl -u myapp.service --since "2026-10-04 08:00:00" --until "2026-10-04 12:00:00"

掌握 4 个命令:接管服务器的进程调度权

管理常驻服务离不开日常的启停与状态干预。掌握这几个指令,就拿到了 Ubuntu 服务器的进程调度权。

# 停止运行中的服务
sudo systemctl stop myapp.service

# 重新启动服务(应用代码更新后常用)
sudo systemctl restart myapp.service

# 撤销开机自启配置
sudo systemctl disable myapp.service

# 列出当前系统中所有正在运行的 service 单元
sudo systemctl list-units --type=service --state=running

常见问题(FAQ)

Q:systemd 的 unit 文件应该放在哪个目录? A:管理员手工编写的服务配置文件统一定义在 /etc/systemd/system/ 目录下。通过 apt 安装的软件,其默认配置文件存放在 /lib/systemd/system/ 目录。修改包默认配置时,应该使用 systemctl edit 命令生成覆盖文件,而不是直接改原文件。

Q:Type=simple 和 Type=forking 有什么区别? A:Type=simple 是默认模式。它假设 ExecStart 调用的命令就是主进程本身,适合大多数直接运行的程序。如果程序启动后会派生出后台子进程,然后直接退出主进程,就需要将类型配置为 Type=forking。

Q:修改了 unit 文件后为什么不生效? A:systemd 会将配置项缓存在内存中以提高响应速度。每次手工修改了文件系统上的配置文件,必须先执行 sudo systemctl daemon-reload 指令。重载磁盘文件后,才能执行启动、重启指令使新配置生效。

停掉手里那些用终端窗口挂载的核心服务,花 10 分钟写一个 unit 文件。把生命周期管理权交还给操作系统底层,服务才算真正在服务器上扎了根。