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.targetgraphical.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 时挂到哪个 targetWantedBy=multi-user.target

systemd vs SysVinit 对比表

场景SysVinit(老方法)systemd(现方法)
启动服务service nginx startsystemctl start nginx
停止服务service nginx stopsystemctl stop nginx
开机自启chkconfig nginx on(或写 /etc/rc.d)systemctl enable nginx
查看状态service nginx statussystemctl status nginx
运行级别runlevel / telinit 3systemctl get-default / isolate multi-user.target
日志/var/log/ 各应用自写 + syslogjournalctl -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 directoryExecStart 路径写错或脚本无执行权限 → 用绝对路径,chmod +x,先手动跑一遍确认
配置里写了 `>` 不生效ExecStart 不支持 shell 语法 → 改成 `/bin/sh -c 'cmdgrep x'`
日志里出现 % 被吃掉unit 中 % 是转义符 → 字面量写 %%,如 ExecStart=/bin/echo 100%%
老脚本服务(自己 fork 成守护进程)启停后 systemd 以为失败了没写 Type=forkingPIDFile= → 声明 Type=forking + PIDFile=/run/app.pid
systemctl stop 卡住很久进程不响应 SIGTERM,等满默认 90s 超时才 SIGKILL → 业务侧优雅退出,或 TimeoutStopSec=10 缩短
服务反复重启(Restart=always 且启动即崩)无限重启循环 → 改 Restart=on-failure + StartLimitIntervalSec,看 systemctl statusn-restarts
journalctl 占用磁盘过大默认上限为磁盘 10% 或 4G 较小者 → journalctl --vacuum-size=200M 立即清,长期改 /etc/systemd/journald.confSystemMaxUse=

常见问题

改完 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.confSystemMaxUse=200Msystemctl restart systemd-journald。日志默认上限是所在磁盘的 10% 或 4G(取较小者)。

服务一直启动失败,最快怎么定位?

systemctl status 服务 看错误与最近日志 → journalctl -u 服务 -n 50 --no-pager 看完整日志 → 手动执行 ExecStart 里的命令验证命令本身能跑(注意环境变量与工作目录差异)。

来源

最后更新:2026-08-11