systemd 是当今几乎所有主流 Linux 发行版(Ubuntu、Debian、RHEL、Arch 等)的默认初始化系统,负责管理开机启动、服务运行、定时任务与系统日志。对运维和开发者来说,systemctl 和 journalctl 是日常使用频率最高的两个命令,而读懂单元文件(Unit File)则是排查服务起不来、反复重启等问题的基本功。
本文基于 Ubuntu 24.04 LTS(systemd 255)与 systemd 最新稳定版 v258(2025 年 9 月发布)展开,从单元文件编写、定时任务到日志排查,全部命令可直接在服务器上照抄执行。
单元文件基础:一切皆单元
systemd 把系统上需要管理的东西抽象为「单元」(Unit),每种单元管理一类对象,常见类型如下:
| 单元类型 | 扩展名 | 作用 |
|---|---|---|
| service | .service | 后台服务/守护进程 |
| timer | .timer | 定时器(替代 cron) |
| socket | .socket | 套接字监听(可配合服务按需启动) |
| mount | .mount | 挂载点 |
| target | .target | 单元组,系统状态(如 multi-user.target) |
| slice | .slice | 资源分组(配合 cgroup v2) |
查看系统上全部服务单元:
systemctl list-units --type=service # 已加载的服务
systemctl list-units --type=service --all # 包含未激活的
systemctl list-unit-files --type=service # 查看 enabled/disabled 状态
systemctl status ssh.service # 查看单个服务的详细状态v258 起 systemd 只支持 cgroup v2,旧的 System V runlevel 支持(initctl、telinit、runlevel0-6.target)已被彻底移除,SysV 脚本也将在 v259 被移除。新写服务请一律使用原生单元文件。
systemctl 命令速查
systemctl start nginx.service # 立即启动
systemctl stop nginx.service # 停止
systemctl restart nginx.service # 重启
systemctl reload nginx.service # 平滑重载配置(服务需支持)
systemctl enable nginx.service # 开机自启
systemctl disable nginx.service # 取消自启
systemctl enable --now nginx.service # 自启 + 立即启动,组合拳
systemctl daemon-reload # 重载单元文件(改完文件必须执行!)
systemctl mask nginx.service # 彻底禁用(连手动启动都不行)
systemctl unmask nginx.service
systemctl is-active nginx.service # 输出 active/inactive,适合脚本判断
systemctl list-dependencies multi-user.target # 查看启动依赖树忘记 daemon-reload 是新手最常见的错误:修改单元文件后直接 systemctl restart,systemd 用的还是旧配置,此时状态会提示 Warning: Unit file changed on disk, 'systemctl daemon-reload' recommended.
先看看系统里当前都在跑哪些服务(systemctl list-units 的输出):

手写一个服务单元文件
把任意命令或脚本包装成系统服务,只需一个 .service 文件。以部署一个简单的 Node.js Web 服务为例,新建 /etc/systemd/system/myapp.service:
[Unit]
Description=My Node.js Web Application
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=www-data
WorkingDirectory=/opt/myapp
ExecStart=/usr/bin/node /opt/myapp/server.js
Restart=on-failure
RestartSec=5
Environment=NODE_ENV=production
EnvironmentFile=-/etc/myapp.env
[Install]
WantedBy=multi-user.target[Unit] 段:Description 是描述;After= 声明启动顺序(本服务在 network 就绪后启动),Wants= 是弱依赖(network 失败不影响本服务)。顺序和依赖要分清:After 只排顺序,Requires/Wants 才建立依赖关系。
[Service] 段是核心,几个关键指令:
| 指令 | 含义 | 常用值 |
|---|---|---|
Type | 启动类型 | simple(默认)、forking、oneshot、notify |
User= / Group= | 以哪个用户运行 | 不要用 root 跑业务服务 |
ExecStart= | 启动命令 | 必须写绝对路径 |
Restart= | 崩溃后重启策略 | no、always、on-failure、on-abnormal |
RestartSec= | 重启间隔秒数 | 防止崩溃后疯狂重启 |
Environment= / EnvironmentFile= | 注入环境变量 | 配置文件可用 - 前缀容忍缺失 |
Type 的选型直接决定服务是否被误判为「已启动」:
simple:ExecStart 一执行就认为服务已启动,适合前台运行的程序(Node、Python 脚本等);forking:程序自己 fork 到后台(老式守护进程,如旧版 nginx),需要配合PIDFile=指定 pid 文件;oneshot:一次性任务,执行完即退出(适合初始化脚本),常配合RemainAfterExit=yes让状态保持 active;notify:程序启动成功后通过 sd_notify 主动通知 systemd,需程序支持(如 systemd 自带的 networkd)。
Restart= 建议统一使用 on-failure(进程异常退出才重启),避免 always 让手动 stop 后又被拉起。
资源限制与安全加固
一条龙限制 CPU、内存和文件句柄,防止服务失控拖垮整机:
[Service]
MemoryMax=512M # 内存硬上限,超出即 OOM kill
MemoryHigh=384M # 软上限,超过会持续回收
CPUQuota=50% # CPU 上限为 1 核的 50%
TasksMax=200 # 最大进程/线程数
LimitNOFILE=65535 # 文件句柄上限sandbox 类指令按需开启,让服务只访问它该访问的东西:
[Service]
ProtectSystem=strict # 整个文件系统只读
ProtectHome=true # 隐藏 /home
PrivateTmp=true # 独立临时目录
ReadWritePaths=/var/lib/myapp # 只放行这几个可写路径
NoNewPrivileges=true # 禁止提权
PrivateDevices=true # 屏蔽设备节点(按需,部分程序需要 /dev)这些指令大多默认关闭(兼容性考虑),对可信部署建议逐步打开——出问题先关掉再对比。
文件位置与优先级
/etc/systemd/system/ # 管理员自定义(优先级最高)
/usr/lib/systemd/system/ # 软件包自带(apt/dnf 安装的)同名的单元文件,/etc/systemd/system 会覆盖 /usr/lib/systemd/system。改完文件记得 systemctl daemon-reload。
改完怎么验证
sudo systemctl daemon-reload
sudo systemctl enable --now myapp.service
systemctl status myapp.service --no-pager # 看状态与最近日志
systemctl show myapp.service -P MainPID # 查主进程 PID
systemctl cat myapp.service # 回显最终生效的配置执行后的真实输出(enable --now 一步完成自启+启动,status 一眼确认状态、主进程与日志):

定时任务:用 systemd timer 替代 cron
cron 的问题:任务执行记录不统一、环境变量与交互式 shell 不同、无资源限制。systemd timer 完全覆盖这些场景,且与 service 单元无缝协作。写法分两步:一个 oneshot 服务 + 一个 timer。
/etc/systemd/system/backup.service:
[Unit]
Description=Daily backup job
[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup.sh/etc/systemd/system/backup.timer:
[Unit]
Description=Run backup every day at 03:30
[Timer]
OnCalendar=*-*-* 03:30:00
Persistent=true # 错过的触发(如关机时段)下次开机补跑
RandomizedDelaySec=60 # 随机延迟,错峰执行
[Install]
WantedBy=timers.target启用并检查:
sudo systemctl enable --now backup.timer
systemctl list-timers --no-pager # 查看所有定时器与下次触发时间
systemctl start backup.service # 手动触发一次(直接跑服务)启用后 list-timers 输出(可以看到刚创建的 backup.timer 已排入队列,下次触发是明天 03:30):

OnCalendar 语法比 cron 更灵活,几个常用写法:
OnCalendar=*-*-* 03:30:00 # 每天 03:30
OnCalendar=Mon..Fri 09:00:00 # 工作日 09:00
OnCalendar=*-*-1,15 00:00:00 # 每月 1 号、15 号零点
OnCalendar=minutely # 每分钟(另有 hourly/daily/weekly/monthly)
OnCalendar=*:0/15 # 每 15 分钟相比 cron 的三个优势:任务状态可用 systemctl status / journalctl -u backup.service 统一查看;可加 MemoryMax 等资源限制;Persistent=true 天然解决「服务器关机错过任务」的问题。
journalctl 日志排查实战
systemd 把服务的 stdout/stderr、内核日志、开机过程统一收进 journal 日志,配合 journalctl 一条命令完成过滤、检索、分析。例如只看 sshd 服务最近 8 条日志:

常用过滤组合
journalctl -u nginx.service --no-pager # 只看某个服务
journalctl -u nginx.service -f # 实时跟踪(同 tail -f)
journalctl --since "1 hour ago" # 最近 1 小时
journalctl --since today --until "1 hour ago" # 时间范围
journalctl -p err -b # 本次开机以来的错误级及以上
journalctl -k # 仅内核消息(类似 dmesg)
journalctl -b -1 # 上一次开机(-2 是上上次)
journalctl --list-boots # 列出所有开机记录
journalctl -g "Failed password|authentication failure" # 正则搜索
journalctl -u myapp.service -p err --since "30 minutes ago" # 组合条件优先级从高到低:emerg(0)、alert(1)、crit(2)、err(3)、warning(4)、notice(5)、info(6)、debug(7)。-p err 表示显示 err 及以上。
排查服务崩溃的标准流程
# 1. 找出失败的服务
systemctl list-units --failed --no-pager
# 2. 查看该服务的错误日志
journalctl -u myapp.service -p err --no-pager
# 3. 如果重启循环,看最近 20 条完整输出
journalctl -u myapp.service -n 20 --no-pager
# 4. 结合本次运行 ID 精确查看某一次运行的全部输出
systemctl show -P InvocationID myapp.service
journalctl _SYSTEMD_INVOCATION_ID=$(systemctl show -P InvocationID myapp.service)
# 5. 崩溃通常是信号 11 (Segmentation fault),全局搜一下
journalctl -g "Segmentation fault|signal 11|core dump" --since "24 hours ago"持久化与磁盘占用控制
Ubuntu 24.04 默认 journal 存在内存 /run/log/journal,重启即丢,且全部服务日志都往里写,长时间运行会占不少磁盘。两步搞定:持久化到磁盘 + 设上限。
sudo mkdir -p /var/log/journal
sudo systemd-tmpfiles --create --prefix /var/log/journal
sudo systemctl restart systemd-journald
journalctl --disk-usage # 查看当前占用编辑 /etc/systemd/journald.conf 的 [Journal] 段:
[Journal]
Storage=persistent # 持久化到 /var/log/journal
SystemMaxUse=2G # journal 总占用上限 2G
SystemMaxFileSize=128M # 单个日志文件大小
MaxRetentionSec=6month # 保留 6 个月手动清理(对应 --vacuum-time、--vacuum-size、--vacuum-files 三种策略):
sudo journalctl --vacuum-size=500M # 压缩到 500M 以内
sudo journalctl --vacuum-time=30d # 只留最近 30 天排查阶段需要完整堆栈信息时,可临时给journalctl加--all参数,避免长字段被截断。
小结
systemd 入门的三件事:用 systemctl 管理服务、用单元文件把脚本变成受管服务、用 journalctl 看日志。掌握了这三个环节,绝大多数服务起不来、反复重启、日志丢失的问题都能独立定位。进阶方向是 socket 激活、systemd-run 临时任务、资源组(slice)管理,以及 v258 引入的 ConcurrencySoftMax= 并发限制等新特性,之后可以单独成文。
参考资料
- systemd v258 Release Notes (GitHub)
- Highlights from systemd v258: part one (LWN)
- systemd man pages(unit、systemctl、journalctl、systemd.timer)
- Filter Systemd Logs with journalctl (ComputingForGeeks)
- Configure Persistent Systemd Journal Storage (ComputingForGeeks)
- Ubuntu 24.04 systemd-journald 日志系统 journalctl 查看日志 (CSDN)
评论 (0)
暂无评论,快来抢沙发吧!