TL;DR
改完 unit 文件第一件事是 systemctl daemon-reload,否则 systemd 仍用旧配置;systemctl restart 是 stop+start,systemctl reload 只发 SIGHUP 不中断进程(服务需支持);脚本里判断状态用 systemctl is-active 服务(退出码 0=active、3=inactive、4=unknown),别解析 status 的文本。最容易踩的坑:① ExecStart 不支持管道/重定向/$VAR,要 shell 语法得写 /bin/sh -c '...';② unit 里 % 是转义符,字面量要写 %%;③ 日志默认上限是磁盘的 10% 或 4G(取较小者),满了 journald 自动轮转,但 journalctl --vacuum-size=200M 可立即清理。
服务生命周期命令表
| 操作 | 命令 | 说明/退出码 |
|---|---|---|
| 启动 | systemctl start 服务 | 已运行则无操作,返回 0 |
| 停止 | systemctl stop 服务 | 先 SIGTERM,超时(默认 90s)后 SIGKILL |
| 重启 | systemctl restart 服务 | = stop + start,适合改配置后 |
| 重载配置 | systemctl reload 服务 | 发 SIGHUP,不中断;服务需实现 |
| 开机自启 | systemctl enable --now 服务 | enable + start 一步到位 |
| 取消自启 | systemctl disable 服务 | 只删符号链接,不停止当前进程 |
| 屏蔽 | systemctl mask 服务 | 彻底禁用,start 都会被拒(含依赖拉起) |
| 查看状态 | systemctl status 服务 | 显示 active/inactive/failed 与最近日志 |
| 脚本判断 | systemctl is-active 服务 | 退出码:0=active、3=inactive、4=unknown、非0=failed |
| 只看是否失败 | systemctl is-failed 服务 | 0=failed,常用于监控告警 |
常用查询命令表
| 需求 | 命令 | 说明 | |
|---|---|---|---|
| 列出正在运行的服务 | systemctl list-units --type=service --state=running | 数量可用 `\ | wc -l` 统计 |
| 列出开机自启服务 | systemctl list-unit-files --state=enabled | 看 enabled/disabled/static | |
| 看服务依赖 | systemctl list-dependencies 服务 | 含 .target 依赖树 | |
| 看服务由谁启动 | systemctl status 服务 的 CGroup 段 | 显示主进程 PID 与子进程 | |
| 看 unit 完整内容 | systemctl cat 服务 | 含 override 合并后结果 | |
| 覆盖部分配置 | systemctl edit 服务 | 生成 drop-in:/etc/systemd/system/<服务>.d/override.conf | |
| 完全重写配置 | systemctl edit --full 服务 | 复制整个 unit 到 /etc 再编辑 | |
| 重启 systemd 本身 | systemctl daemon-reload | 改过 unit/新装 unit 后必跑 | |
| 默认运行级别 | systemctl get-default | 通常 multi-user.target 或 graphical.target | |
| 切换运行级别 | systemctl isolate multi-user.target | 类似旧版 runlevel 3 |
unit 文件核心字段(写 .service 直接抄)
| 段/字段 | 作用 | 例子 |
|---|---|---|
[Unit] Description= | 人类可读描述,systemctl status 显示它 | Description=My Web API |
[Unit] After= | 启动顺序(不构成依赖,只排序) | After=network-online.target |
[Unit] Wants= / Requires= | 弱/强依赖;Requires 失败会连带本服务失败 | Wants=mysql.service |
[Service] Type= | 进程类型:simple(默认,ExecStart 常驻)/ forking(主进程 fork 后父进程退出,需 PIDFile=)/ notify(进程主动发 READY=1,需 sd_notify) | Type=simple |
[Service] ExecStart= | 启动命令,不支持管道/重定向/$VAR;要 shell 用 /bin/sh -c '...';% 需写成 %% | ExecStart=/usr/bin/python3 /opt/app/main.py |
[Service] Environment= / EnvironmentFile= | 环境变量;文件每行 KEY=VALUE,文件不存在会导致启动失败 | EnvironmentFile=/etc/app.env |
[Service] User= / Group= | 降权运行,安全必配 | User=www-data |
[Service] Restart= | 失败重启策略:no(默认)/on-failure/always/on-abnormal;配 RestartSec=3 防抖 | Restart=on-failure |
[Service] WorkingDirectory= | 工作目录,相对路径的 ExecStart 从这解析 | WorkingDirectory=/opt/app |
[Service] ExecStartPre= / ExecStartPost= | 启动前/后钩子(可多个,按顺序执行) | ExecStartPre=/bin/mkdir -p /run/app |
[Install] WantedBy= | 被 enable 时挂到哪个 target | WantedBy=multi-user.target |
systemd vs SysVinit 对比表
| 场景 | SysVinit(老方法) | systemd(现方法) |
|---|---|---|
| 启动服务 | service nginx start | systemctl start nginx |
| 停止服务 | service nginx stop | systemctl stop nginx |
| 开机自启 | chkconfig nginx on(或写 /etc/rc.d) | systemctl enable nginx |
| 查看状态 | service nginx status | systemctl status nginx |
| 运行级别 | runlevel / telinit 3 | systemctl get-default / isolate multi-user.target |
| 日志 | /var/log/ 各应用自写 + syslog | journalctl -u nginx 集中收集 |
| 启动脚本 | /etc/init.d/nginx(shell 脚本) | /etc/systemd/system/nginx.service(声明式) |
可直接抄的完整 unit 示例(Python 服务)
# /etc/systemd/system/myapp.service
[Unit]
Description=My Python Web API
After=network-online.target mysql.service
Wants=mysql.service
[Service]
Type=simple
User=www-data
Group=www-data
WorkingDirectory=/opt/myapp
EnvironmentFile=/etc/myapp.env
ExecStart=/usr/bin/python3 -m uvicorn main:app --host 127.0.0.1 --port 8000
Restart=on-failure
RestartSec=3
[Install]
WantedBy=multi-user.target
# 部署一个服务(含权限与重载)
sudo cp myapp.service /etc/systemd/system/myapp.service
sudo chmod 644 /etc/systemd/system/myapp.service
sudo systemctl daemon-reload
sudo systemctl enable --now myapp
# 验证
systemctl is-active myapp # 输出 active,退出码 0
systemctl status myapp --no-pager | head -n 15
journalctl -u myapp -n 50 --no-pager # 最近 50 行日志
# 脚本里判断服务状态(不解析 status 文本,用退出码)
import subprocess
r = subprocess.run(["systemctl", "is-active", "myapp"], capture_output=True, text=True)
print(r.returncode, r.stdout.strip()) # 0 active / 3 inactive / 非0 failed
# 用 sdnotify 实现 Type=notify(systemd 收到 READY=1 才认为启动完成)
try:
import sdnotify
n = sdnotify.SystemdNotifier()
n.notify("READY=1")
except ImportError:
pass # 本地调试时无 systemd 也能跑
高频坑排查
| 现象 | 原因 → 解法 | ||
|---|---|---|---|
| 改了 unit 文件重启没变化 | 没跑 systemctl daemon-reload → 每次改完先 reload 再 restart | ||
| 服务启动即失败,日志只有几行 | 最常见是 EnvironmentFile= 指向的文件不存在 → 确认路径与权限(文件需可读,root 启动也要看 644) | ||
报错 Failed to execute: No such file or directory | ExecStart 路径写错或脚本无执行权限 → 用绝对路径,chmod +x,先手动跑一遍确认 | ||
| 配置里写了 ` | 或 >` 不生效 | ExecStart 不支持 shell 语法 → 改成 `/bin/sh -c 'cmd | grep x'` |
日志里出现 % 被吃掉 | unit 中 % 是转义符 → 字面量写 %%,如 ExecStart=/bin/echo 100%% | ||
| 老脚本服务(自己 fork 成守护进程)启停后 systemd 以为失败了 | 没写 Type=forking 和 PIDFile= → 声明 Type=forking + PIDFile=/run/app.pid | ||
systemctl stop 卡住很久 | 进程不响应 SIGTERM,等满默认 90s 超时才 SIGKILL → 业务侧优雅退出,或 TimeoutStopSec=10 缩短 | ||
| 服务反复重启(Restart=always 且启动即崩) | 无限重启循环 → 改 Restart=on-failure + StartLimitIntervalSec,看 systemctl status 的 n-restarts | ||
| journalctl 占用磁盘过大 | 默认上限为磁盘 10% 或 4G 较小者 → journalctl --vacuum-size=200M 立即清,长期改 /etc/systemd/journald.conf 的 SystemMaxUse= |
常见问题
改完 systemd 服务配置为什么不生效?
几乎都是没执行 systemctl daemon-reload。systemd 启动时把 unit 读进内存,改文件后必须 daemon-reload 重新加载,再 systemctl restart 服务 才会用新配置。
systemctl status 显示 active (exited) 是正常的吗?
正常。exited 表示服务按预期运行完主进程就退出了(如一次性任务、Type=oneshot 的初始化脚本),与 active (running) 一样算健康;只有 failed 才是异常。
怎么让服务开机自启?
sudo systemctl enable 服务 即可(enable --now 同时启动)。unit 文件里要有 [Install] 段和 WantedBy=multi-user.target,否则 enable 会报错。取消自启用 disable,彻底禁止用 mask。
journalctl 日志太多占满磁盘怎么办?
先 journalctl --vacuum-size=200M 立即释放空间;要长期限制,在 /etc/systemd/journald.conf 设 SystemMaxUse=200M 后 systemctl restart systemd-journald。日志默认上限是所在磁盘的 10% 或 4G(取较小者)。
服务一直启动失败,最快怎么定位?
systemctl status 服务 看错误与最近日志 → journalctl -u 服务 -n 50 --no-pager 看完整日志 → 手动执行 ExecStart 里的命令验证命令本身能跑(注意环境变量与工作目录差异)。
来源
- systemd 官方文档(freedesktop.org/software/systemd),核实日期 2026-08-11
- systemctl 手册页(man systemctl / man systemd.service / man journalctl)
- Arch Linux Wiki: systemd 页面(wiki.archlinux.org/title/Systemd),核实日期 2026-08-11