系统与运维

systemd 服务启动失败排查:从 status 到依赖与时序死锁

看出 systemctl is-failed 只是第一步,本文把失败分成"启动器直接报错、进程秒退、依赖未就绪、端口迟迟不监听"四类,并给出每一类对应的日志入口与修复手法。

作者:巧匠团队·7 分钟阅读·更新于 2026-09-06

拿到第一手状态:status 不等于日志

新手最常见的动作是把 systemctl status 的输出贴在论坛里,其实 status 只给了你定时的摘要:Active 状态、主 PID、最近几次退出码。它内置的几行日志只是"最近",容易被紧随其后的成功重启覆盖掉。

真正该看的是全额日志:journalctl -u 服务名 不带 -n 限制地翻,看启动时打印的第一条报错,往往一个断言或路径写错就写在启动序列前几行。同时对比开机后首次启动与手动 systemctl restart 的行为差异——开机态经常会因为乱序依赖而失败。

systemctl status myapp.service
systemctl is-failed myapp.service
journalctl -u myapp.service -n 500 --no-pager
# 只看启动那条线的完整输出
journalctl -u myapp.service --since=-2h | grep -iE "error|fail|denied|cannot" 

四类失败的判断要点

第一类"launcher 器直接报错"是 systemd 压根没能 exec:常常是 bin 路径不存在、权限不对或 shbang 写错,journal 里会有 Failed to execute。第二类"进程秒退"指主进程自己起来几秒内崩了——崩溃栈在应用日志,而 status 里能看到 exit-code 与驻留时间。

第三类"依赖未就绪"常见于数据库服务启动了但它要连的 Redis/Mongo 其实还在拉起中,表现为启动成功但几个服务互相等。第四类"端口迟迟不监听"是绑定失败的典型征兆,地址已被占用或权限不足,ss -lnt 里始终没有对应项。掌握这四类,就能把"为什么起不来"收敛成具体一行。

ls -l /usr/lib/systemd/system/myapp.service
# 一二类的证据栏
cat /etc/systemd/system/myapp.service | grep -E "ExecStart|ExecStartPre"
ss -lnt | grep 8080
# 三类的依赖链
systemctl list-dependencies myapp.service

依赖与时序死锁:After 和 Requires 的常见误用

很多人把 After 当成"等我先起来",把 Requires 当成"我依赖它",逻辑上是 mssql:Requires 只表达"没有它我就起",After 才表达先后顺序。只用 Requires 不开 After,会导致两个服务雷同地同时竞速启动,弱依赖常在此刻悄悄失败。

更麻烦的是时序死锁:如果 A 的 After=B 且 B 的 After=A,systemd 会直接拒载整个事务,把二者一起 dropped。发现系统卡在启动待机时,用 systemd-analyze verify 检查 cycle,用 systemd-analyze blame 看谁拖慢了启动。

# 错误示范:只有 Requires 没有 After 导致弱依赖竞速
[Unit]
Requires=redis.service

# 修复对照:补上顺序与依赖语义
[Unit]
Requires=redis.service
After=redis.service network-online.target
Wants=network-online.target

# 校验周期/耗时
systemd-analyze verify myapp.service
systemd-analyze blame | head -10

sandbox 选项背锅:ProtectedPaths / User 把自己锁死

systemd 的安全隔离选项(ProtectSystem、ProtectHome、ReadOnlyPaths、User=)非常好用,但也特别容易让服务"明明配置正确却起不来"。比如 ProtectSystem=strict 会把你配置里指向 /var 的读写路径全部逼成只读,数据库第一次建表就失败。

解法是逐条关一半再审:先注释掉可疑的隔离项保留 User/Group 与核心路径,确认能启动后再逐步放开。用 systemd-analyze security 查看这单位的攻击面评分,同时记住 + 前缀可以显式放行某个路径而不推翻全局。

systemd-analyze security myapp.service
# 错误示范:把 /var/lib 误设为只读
# ProtectSystem=strict

# 修复对照:只读保护 + 显式放行业务目录
ProtectSystem=strict
ReadWritePaths=/var/lib/myapp /run/myapp
# 或在路径前用 + 允许
[Service]
User=myapp
ProtectHome=read-only

验证:从失败态到持续运行的正解检查清单

修完之后别只看 Active: active (running) 就收工,要同时确认三件事:重启后 status 显示 active (running) 且无 restart 计数暴增;端口在 ss 里保持监听至少 60 秒;journal 里不再新增 error。对依赖类故障,主动把依赖关一次再拉起,验证时序是不是稳定复现。

如果服务仍有偶发退出,用 systemctl reset-failed 清掉计数后再观察,避免把历史失败统计误当成新故障。最后可把整改前后的启动耗时对比存档,作为回归基线。

systemctl reset-failed myapp.service
systemctl restart myapp.service
# 观察 60s
sleep 60
systemctl show myapp.service -p NRestarts -p ActiveState
ss -lnt | grep 8080
journalctl -u myapp.service -n 30 --since=-5min | grep -c error

官方参考来源

下方为命令对应的官方权威文档,供你核对最新用法与深入查阅。