生产 Linux 主机上的 systemd 服务出现“启动后立即退出、反复重启或启动超时”时,先不要直接修改 Restart=、连续执行 systemctl restart,也不要只根据服务当前状态判断原因。更稳妥的故障排查路径是:先保护现场并确认影响范围,再读取服务状态和日志,随后用退出码、依赖关系、权限、端口与资源信息验证假设,最后以可回滚的最小变更恢复服务,并通过连续运行和健康检查确认故障真正结束。

先保护现场:确认影响范围并保留证据
判断是单个服务故障还是链路故障
先记录当前时间、主机名、服务名和受影响的业务入口。以下命令适合在不改变服务状态的情况下收集基础信息:
date -Is
hostname
systemctl is-active example.service
systemctl is-enabled example.service
systemctl status example.service --no-pager -l
重点观察:
Active:是failed、activating、inactive还是短暂进入active后又退出。Result:是否显示exit-code、timeout、signal或其他失败结果。Main PID是否反复变化,说明主进程可能被持续拉起。- 日志中是否有
Start request repeated too quickly,这表示启动请求触发了 systemd 的启动频率限制,不等于根因已经找到。 - 服务是否只是被监控系统标记为异常,还是已经影响端口、依赖服务和业务请求。
同时检查服务监听状态和相关进程:
ps -ef | grep -E '[e]xample|[r]elated-process'
ss -lntup
systemctl list-dependencies example.service
如果服务已停止但端口仍被其他进程占用,排查重点应转向端口冲突;如果服务和关键依赖都失败,则先确认依赖链上的最早失败节点,而不是只反复重启最上层服务。
保留当前状态、日志和配置快照
在执行清理、编辑配置或手工启动前,先保存状态信息:
mkdir -p /var/tmp/example-incident-$(date +%Y%m%d-%H%M%S)
case_dir=/var/tmp/example-incident-$(date +%Y%m%d-%H%M%S)
systemctl status example.service --no-pager -l > "$case_dir/status.txt"
systemctl show example.service > "$case_dir/show.txt"
systemctl cat example.service > "$case_dir/unit.txt"
journalctl -u example.service --since "-30 min" --no-pager > "$case_dir/journal.txt"
如果服务日志量较大,可先查看最近事件,再按时间窗口导出:
journalctl -u example.service -b --since "10 minutes ago"
-o short-precise --no-pager
不要在尚未保存日志前使用会清理现场的操作。对于正在快速重启的服务,可先停止自动拉起以便读取稳定现场,但这会扩大服务中断时间,应在确认影响范围并完成变更记录后执行:
sudo systemctl stop example.service
如果服务由上层 target、监控程序或编排工具重新拉起,单独停止可能不够,还应查看谁在触发启动:
systemctl list-dependencies --reverse example.service
systemctl show example.service -p WantedBy -p RequiredBy
读取 systemd 证据:先区分退出、超时和被终止
查看服务实际生效的配置
不要只看 /etc/systemd/system/example.service。服务可能来自发行版目录、管理员 drop-in 目录或其他覆盖配置。使用以下命令查看合并后的内容:
systemctl cat example.service
systemctl show example.service
-p FragmentPath
-p DropInPaths
-p ExecStart
-p User
-p Group
-p WorkingDirectory
-p EnvironmentFiles
-p Restart
-p RestartSec
-p TimeoutStartUSec
-p StartLimitIntervalUSec
-p StartLimitBurst
这里需要区分两类信息:
systemctl cat展示 unit 文件和 drop-in 的文本内容,适合确认配置来源、覆盖顺序和命令行参数。systemctl show展示 systemd 当前解析后的属性,适合确认真正生效的值。
如果最近改过 unit 文件或 drop-in,必须执行:
sudo systemctl daemon-reload
daemon-reload 只让 systemd 重新读取 unit 配置,不会自动启动或重启服务。配置变更后是否执行 restart,应根据影响范围和发布窗口决定。
判断退出码及其含义
查看最近一次状态和主进程退出信息:
systemctl status example.service --no-pager -l
systemctl show example.service
-p ExecMainCode
-p ExecMainStatus
-p ExecMainStartTimestamp
-p ExecMainExitTimestamp
-p Result
常见判断方式如下:
ExecMainCode=exited且退出状态非零:主进程主动退出,优先检查配置解析、参数、文件路径、权限和依赖连接。ExecMainCode=killed:进程被信号终止,检查是否收到SIGTERM、SIGKILL,以及内核日志中是否出现 OOM 相关记录。Result=timeout:systemd 在启动阶段等待超时,重点检查进程是否卡在网络连接、挂载、DNS、设备、外部依赖或初始化任务。- 状态显示成功但服务很快变成
inactive:可能是Type=与实际进程模型不匹配,或该程序本身是一次性任务而不是常驻进程。 - 服务短时间失败多次后不再尝试:检查启动频率限制,不要把限制解除当成根因修复。
Restart=决定 systemd 在特定退出条件下是否尝试重新启动服务。它能影响故障表现,但通常不能修复导致进程退出的配置、权限或依赖问题。临时修改 Restart=前,应先确认修改目的、预期观察时间以及回滚方式。
从日志中定位“最早失败原因”
按服务查看日志:
journalctl -u example.service -b --no-pager -o short-precise
journalctl -u example.service -b -p warning..alert --no-pager
如果服务由其他 unit 启动或依赖多个组件,还要扩大范围:
journalctl -b --since "15 minutes ago"
-u example.service
-u example-dependency.service
--no-pager
优先寻找第一次启动失败时出现的错误,而不是最后一条“服务重启失败”。例如:
No such file or directory:确认路径、挂载点和生成文件是否存在。Permission denied:确认运行用户、目录权限、文件标签或受限访问策略。Address already in use:检查端口占用者和监听地址。Failed to connect、Connection refused:确认依赖服务是否已就绪,以及依赖地址和端口是否正确。Start request repeated too quickly:说明重试已受到限制,应回到此前的退出日志。Timed out:确认是 systemd 启动等待超时,还是应用自身对外部操作设置的超时。
按假设逐层排查
分支一:配置文件、参数或环境变量错误
先查看 unit 中的启动命令、工作目录和环境文件:
systemctl cat example.service
systemctl show example.service -p ExecStart -p WorkingDirectory -p EnvironmentFiles
再检查相关文件:
namei -l /etc/example/example.conf
ls -l /etc/example/example.conf
test -r /etc/example/example.conf && echo "config readable"
如果程序支持前台校验、配置测试或只读检查模式,应使用与 unit 相同的运行用户和参数执行。例如:
sudo -u example --
/usr/local/bin/example --config /etc/example/example.conf --check
命令和参数必须以实际软件支持的语法为准,不要把示例中的 --check 当作所有程序通用选项。
预期结果是配置检查明确成功,且没有缺失文件、非法参数或无法解析的值。若检查失败,优先恢复最近一次已知可用配置,而不是直接扩大启动超时时间。
建议采用备份后替换的方式:
sudo cp -a /etc/example/example.conf
/etc/example/example.conf.$(date +%Y%m%d-%H%M%S).bak
sudo install -o example -g example -m 0640
/var/tmp/example.conf.known-good
/etc/example/example.conf
回滚条件包括:配置校验失败、服务启动后立即退出、日志出现新的解析错误,或健康检查未恢复。回滚后再次执行配置校验,再决定是否启动服务。
分支二:依赖服务未就绪或启动顺序不完整
先列出直接依赖和反向依赖:
systemctl list-dependencies example.service
systemctl list-dependencies --reverse example.service
systemctl show example.service -p Requires -p Wants -p After -p Before
需要注意,After=主要表达启动顺序,不等于依赖服务一定会被拉起,也不等于服务已经具备业务可用性。Requires=和Wants=表达拉起或关联关系,但依赖服务“处于 active”也不一定代表端口、数据库或外部接口已经可用。
检查依赖服务状态和日志:
systemctl is-active example-dependency.service
systemctl status example-dependency.service --no-pager -l
journalctl -u example-dependency.service -b --since "15 minutes ago" --no-pager
如果依赖是网络端点、挂载点或本地套接字,还要验证实际资源:
getent hosts dependency.internal
ss -lntup
findmnt /data
test -S /run/example/dependency.sock && echo "socket exists"
修复顺序应是先恢复最底层失败依赖,再启动上层服务。只有当依赖已经稳定、连接测试成功而服务仍失败时,才继续检查服务自身配置。若最近变更了依赖地址、挂载配置或启动顺序,应回滚对应变更,并记录回滚后依赖恢复时间和服务启动结果。
分支三:运行用户、目录和权限问题
systemd 服务常以专用用户运行。以管理员身份执行配置检查成功,并不能证明服务用户也能访问相关文件。确认实际身份:
systemctl show example.service -p User -p Group -p SupplementaryGroups
检查从根目录到目标文件的每一级权限:
namei -l /etc/example/example.conf
namei -l /var/lib/example
namei -l /var/log/example
必要时以服务用户进行最小化验证:
sudo -u example test -r /etc/example/example.conf
sudo -u example test -x /var/lib/example
sudo -u example sh -c 'touch /var/lib/example/.write-test'
sudo -u example rm -f /var/lib/example/.write-test
如果写测试失败,不要直接对整个目录执行宽泛的 chmod -R 777。应根据服务需要修正属主、属组和最小权限:
sudo chown example:example /var/lib/example
sudo chmod 0750 /var/lib/example
修改前要确认目录中是否存在其他应用数据,以及权限变更是否会影响备份、轮转或安全策略。若系统启用了额外的访问控制机制,还要结合系统日志判断拒绝原因,避免仅凭普通 Unix 权限下结论。
分支四:监听端口、残留进程或进程模型异常
服务启动失败时,先确认目标端口是否被占用:
ss -lntup | grep ':8080'
若系统提供对应工具,也可以查询占用者:
lsof -nP -iTCP:8080 -sTCP:LISTEN
判断重点包括:
- 占用端口的是否是同一服务的旧进程。
- 是否有另一个服务使用了相同端口。
- unit 中配置的监听地址是否与实际需求一致。
- 服务是否绑定 IPv4、IPv6 或 Unix 套接字中的不同地址。
- 应用进程是否已退出,但子进程仍持有端口。
不要仅凭 PID 就执行强制终止。先确认进程归属、启动命令和父子关系:
ps -o pid,ppid,user,stat,lstart,cmd -p <PID>
systemctl status example.service --no-pager -l
如果服务采用派生进程或后台化模式,应检查 Type=、PIDFile=及程序实际行为是否一致。将一个前台运行程序错误配置为需要 PID 文件,或将会自行后台化的程序按前台类型管理,都可能造成 systemd 对服务状态的判断与实际进程不一致。此类修改应在 unit 变更评审后进行,并保留原配置以便回滚。
分支五:资源限制、内核终止和启动超时
先查看 unit 的资源和启动限制:
systemctl show example.service
-p LimitNOFILE
-p LimitNPROC
-p MemoryMax
-p TasksMax
-p CPUQuotaPerSecUSec
-p TimeoutStartUSec
再检查内核和系统日志:
journalctl -k -b --since "15 minutes ago" --no-pager
dmesg -T | tail -n 100
如果看到 OOM、资源耗尽、文件描述符不足或任务数达到限制,应先确认实际消耗和限制来源,再决定是否调整参数。调整 MemoryMax、TasksMax、LimitNOFILE或 TimeoutStartSec之前,至少记录:
- 当前限制和实际使用量;
- 哪条日志证明限制是失败原因;
- 提高限制后的风险和上限;
- 观察时长、监控指标和回滚值。
启动超时不应默认通过无限增大 TimeoutStartSec解决。先确认进程是否仍在初始化、卡在依赖访问,还是已经失去响应:
systemctl status example.service --no-pager -l
ps -o pid,ppid,stat,wchan:32,etime,cmd -p <PID>
如果进程一直等待挂载、DNS、远端连接或设备,应修复对应依赖;只有在确认初始化确实正常但合理耗时超过现有阈值时,才考虑调整超时,并在变更后观察启动时长和失败率。

最小修复与安全回滚
变更前先确认对象和回滚点
对于 unit、环境文件和应用配置,至少保留以下信息:
systemctl cat example.service > /var/tmp/example.service.before
systemctl show example.service > /var/tmp/example.show.before
sha256sum /etc/example/example.conf
变更时尽量只改一个变量。例如先恢复配置文件,再验证;不要同时修改端口、运行用户、超时和 Restart=,否则即使服务恢复,也无法判断真正原因。
修改 unit drop-in 时,可使用:
sudo systemctl edit example.service
sudo systemctl daemon-reload
systemctl cat example.service
确认合并结果正确后,再按变更窗口启动或重启。若变更导致启动失败,回滚 drop-in 或配置文件,执行 daemon-reload,然后重新验证。不要通过删除整个 /etc/systemd/system目录或覆盖发行版 unit 的方式处理单个服务故障。
谨慎处理 Restart 和启动频率限制
Restart=适合定义服务在特定异常退出后的恢复策略,但不应代替根因修复。持续重启可能造成:
- 日志快速增长;
- 依赖服务被反复连接或断开;
- 端口、锁文件和临时文件状态不稳定;
- 真实失败原因被后续重启日志淹没;
- 触发 systemd 的启动频率限制。
如果为了保护现场临时停止服务,修复根因后再恢复原有策略。若确实需要调整 RestartSec=或启动频率限制,应明确这是策略变更,而不是故障修复,并设置观察窗口和恢复原值的条件。
恢复后的验证不能只看 active
验证服务连续运行
启动服务后先查看完整状态:
sudo systemctl start example.service
systemctl status example.service --no-pager -l
systemctl is-active example.service
然后在一段足以覆盖正常初始化和业务探测周期的时间内连续观察:
watch -n 5 'systemctl is-active example.service; systemctl show example.service -p MainPID -p NRestarts -p Result'
NRestarts是否继续增长、MainPID是否频繁变化,是判断是否仍在重启的重要线索。观察期间同步查看日志:
journalctl -fu example.service
确认没有新的配置错误、权限拒绝、依赖连接失败、超时或资源耗尽记录。
验证端口、进程和业务健康检查
服务处于 active后,继续验证实际工作状态:
ss -lntup | grep ':8080'
ps -o pid,ppid,user,stat,etime,cmd -p "$(systemctl show -p MainPID --value example.service)"
如果服务提供健康检查接口,应从与生产流量相近的网络位置执行,而不是只在本机检查进程存在:
curl --fail --silent --show-error
--connect-timeout 3 --max-time 5
http://127.0.0.1:8080/health
实际路径、端口和协议应以服务配置为准。健康检查通过后,还要确认一条最小业务链路,例如读取一个只读状态、建立一次受控连接或完成一条无副作用的探测请求。
确认告警恢复和变更闭环
最后检查监控系统中的服务状态、端口探测、日志告警和依赖告警是否恢复。若告警仍未恢复,不要仅以 systemctl status 为准,继续核对:
- 监控探针是否访问了正确地址和端口;
- 服务是否只监听本地地址;
- 健康检查是否因权限、证书或依赖问题失败;
- 告警是否仍处于延迟恢复或抑制状态;
- 回滚是否只恢复了进程,却没有恢复业务配置。
故障记录中应留下失败时间线、最早错误、验证过的假设、实际变更、回滚条件、恢复时间和后续防护项。这样下一次遇到 Linux 运维中的 systemd 服务反复重启问题时,排查可以从证据开始,而不是从反复重启开始。

发布者:jacky,转转请注明出处:https://kubiyun.com/archives/4576
评论列表(7条)
先找第一次报错,别被反复重启的表象带偏
管理员能读,不代表服务用户也能读
端口还被旧进程占着,重启再多次也白搭 @jacky
@黛玉葬花:对,先查清端口占用者及其归属,再处理旧进程;否则反复重启只会继续失败。
active不等于业务真的恢复
@Old Time Ollie:对,active只说明进程暂时在运行,端口、依赖和实际健康检查都通过,才算业务真正恢复。
改完 unit 别忘了 daemon-reload,它不会顺手重启服务