首页/系统/Linux 运维命令速查/场景:服务起不来七步排查法
systemd 与服务42 条

场景:服务起不来七步排查法

服务 systemctl start 失败:从语法、环境、权限、端口、依赖到资源逐层排除,标准动作一次跑完定位根因。

Linux 运维命令速查系统更新于 2026-10-13

场景概述

systemctl start 失败时,systemd 给出的信息往往只有「退出码非 0」或控制进程退出,真正的根因要自己剥。失败集中在四层:单元定义本身(ExecStart 路径、语法、Type 判断)、运行环境(User、PATH、工作目录、环境变量、文件权限)、资源与依赖(端口占用、依赖未启动、内存与文件描述符上限、SELinux 拦截)、以及应用自身逻辑(配置错误、连不上数据库)。这套流程的目的,是把 systemd 当时的运行环境在终端里复现出来。

场景信息

触发条件systemctl start 报 Job failed,或服务启动后立刻进入 inactive/failed
排查目标定位到具体失败层,改对地方而不是反复重启碰运气
工具链status 看状态 → journalctl 看日志 → systemd-analyze verify 查语法 → 手动复现环境 → 逐项排除资源与权限
退出码读法1 到 199 由应用自身返回;200 以上是 systemd 保留段,例如 203 表示无法执行命令(路径不存在、无执行权限、二进制格式不匹配),217 表示切换 User 失败

四个失败层

  • 单元定义层:ExecStart 路径写错、脚本缺少可执行权限或 shebang、Type 与实际启动方式不符
  • 运行环境层:User 不存在、WorkingDirectory 不存在或不可读、依赖的环境变量没在单元里声明
  • 资源与依赖层:端口被占用、依赖服务未启动、文件描述符或内存上限过低、安全模块拦截
  • 应用逻辑层:配置文件语法错误、证书路径不对、连不上数据库或上游服务

七步排查命令

七步排查命令
# 第 1 步:状态与退出码
systemctl status myapp --no-pager -l
systemctl show myapp -p ExecMainStatus,Result,NRestarts
# 第 2 步:完整日志
journalctl -u myapp -n 200 --no-pager
# 第 3 步:单元语法与依赖
systemd-analyze verify /etc/systemd/system/myapp.service
systemctl list-dependencies myapp | head
# 第 4 步:手动复现同环境
sudo -u appuser env -i PATH=/usr/bin:/bin /opt/myapp/bin/start --config /etc/myapp/app.conf; echo "exit=$?"
# 第 5 步:权限与路径
namei -l /opt/myapp/bin/start; ls -ld /opt/myapp /etc/myapp
# 第 6 步:端口冲突
ss -lntp | grep -E ':8080'
# 第 7 步:资源与安全模块
journalctl -u myapp | grep -iE 'oom|limit|denied|permission' | tail
ausearch -m avc -ts recent 2>/dev/null | tail   # SELinux 拦截

现象对照根因

现象常见根因定位命令
status=203/EXECExecStart 路径不存在、无执行权限、脚本缺 shebang 或有 Windows 换行符ls -l、file、head -1 查看脚本首行
status=217/USER单元里的 User= 账号不存在id 用户名
Address already in use旧实例未退干净,或被其他服务占用端口ss -lntp、ps -ef 双证确认
启动成功但立刻 inactiveType 判断错误:前台进程 daemonize 后父进程退出,应改为 Type=forking对照应用启动方式与单元 Type
日志里 Permission deniedWorkingDirectory 或日志目录属主不对,也可能是 SELinux 上下文namei -l 路径、ausearch -m avc
反复重启后被拒绝启动触发启动频率限制(StartLimitBurst)systemctl reset-failed 后重新排查
systemd-analyze verify 只能检查单元语法、部分路径与依赖关系,不校验应用逻辑,通过它不代表服务能起来。它的价值在于快速排除「单元文件写错」这一层,避免在语法问题上绕圈。
手工用 sudo -u 复现要注意差异:systemd 会重置环境变量、设置独立的 cgroup、按 Type 判断启动是否成功。需要精确复现时用 systemd-run 启动一次临时单元:systemd-run --unit=test -p User=appuser -p WorkingDirectory=/opt/myapp /opt/myapp/bin/start,再用 journalctl -u test 看输出。
Restart=always 会让配置错误的应用无限重启,既掩盖真实日志又可能打满 CPU 与磁盘。排查阶段先 systemctl stop 停掉,确认根因后再启用 Restart=on-failure 并配合 StartLimitIntervalSec 与 StartLimitBurst 限制频率。
顺序上先廉价后昂贵:status 与 journalctl 是零成本的信息源,先把这两步看完再动手改配置。改完单元文件必须 systemctl daemon-reload 让 systemd 重新读取,否则启动的仍是旧定义;改完用 systemd-analyze verify 复检,再 restart 并立刻用 journalctl -u 单元名 -n 50 确认新日志里报错是否消失。

证据位置对照

现象先看哪里再看哪里
启动即失败单元状态与执行错误码路径与文件权限
运行中退出退出码与标准错误内核与资源记录
端口冲突监听状态与进程列表是否有残留实例
权限被拒服务账号与文件属主安全模块的审计记录
依赖不通下游服务与网络状态单元里的依赖声明
排查顺序遵循「先看单元再进应用」:单元层的错误码指向路径、权限、格式这类启动条件,应用日志回答业务逻辑为什么失败。顺序颠倒时容易在应用日志里翻很久却找不到任何线索,因为问题根本没走到应用代码那一层。

易错点

  • 看到「退出码非 0」就埋头改应用配置,忽略了 203 这类 systemd 层的执行失败(路径、权限、格式)
  • 用 sudo -u 手动执行成功就断定单元没问题,忽略了 systemd 在环境变量、工作目录、cgroup 与 Type 判断上的差异
  • 端口冲突只翻应用日志,不去查旧实例是否残留:ps 与 ss 的证据要一起看
  • 反复 restart 触发启动频率限制后被拒绝启动,误以为 systemd 出问题;systemctl reset-failed 清除状态后再排查
  • SELinux 或 AppArmor 的拦截不会写进应用日志,只在审计日志里出现,排查时容易反复怀疑配置文件
  • 跳过单元层直接翻应用日志:启动条件不满足时应用可能一行日志都没写,白找半天,先确认单元状态与退出码能省下大量时间

数据来源与参考

  • man 5 systemd.service
  • man 1 systemd-analyze(verify)
  • systemd 官方 Troubleshooting 文档

相关工具

​‌‌​​‌​​​‌‌​‌‌‌‌​‌‌​​​‌‌​‌‌‌​​‌‌​​‌​‌‌‌​​‌‌‌​​​‌​‌‌​‌​​‌​‌‌‌​​​‌​‌‌​‌​​‌​‌‌​​​‌‌​‌‌‌​‌​​​‌‌​‌‌‌‌​​‌​‌‌‌​​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​‌‌​‌