Systemd 服务启动失败时,只给一句 Failed to start 往往让人无从下手。这条提示只是结果,具体原因需要从状态、日志、单元文件三个方向逐个排除。先做哪个动作,取决于你想多快定位到问题。
先确认服务状态与退出原因
遇到服务启动失败时,先别急着改单元文件。运行 systemctl status 是第一步,它能直接显示服务当前状态、主进程 PID、退出码以及最近几行日志。如果状态是 failed,输出里通常包含条件:比如 ExecStart 指定的程序不存在、权限被拒绝、或启动后立刻退出。此时注意看 status 输出的 Active 行和 Process 行,它们分别给出激活时间和退出原因。若输出中没有足够上下文,再用 journalctl 进一步拉取日志,避免凭猜测修改配置。
status 输出里还有一个容易被忽略的字段是 Loaded,它显示单元文件是否被正确加载,以及是否被其他服务 mask。如果 Loaded 后面出现 bad-setting 或 masked,问题可能出在配置加载阶段,而不是程序执行阶段。另外,Process 行会给出 EXEC 或 SIGNAL 等标志,比如 exit-code 表示程序主动返回错误码,signal 表示进程被信号杀死,这两类问题的排查方向完全不同。
用 journalctl 拉取服务专属日志
journalctl -u -n 200 --no-pager 可以快速查看该服务最近 200 行日志。若服务反复重启,加上 -f 跟踪输出,能观察每次尝试的失败点。日志中常见的线索包括:配置文件解析错误、监听端口被占用、依赖的服务未启动。如果日志时间戳与启动时间对不上,可能是时钟漂移或日志轮转,需要检查 /var/log/journal 或 /run/log/journal 是否有对应记录。建议同时执行 journalctl -xe,它会合并系统级错误和最近会话,帮助发现跨服务依赖问题。
这条命令的适用场景是服务已经启动过至少一次,并且 systemd 捕获到了标准输出或错误输出。如果服务使用 Type=forking,日志可能分散在多个进程里,此时需要借助 _PID 或 _COMM 过滤,比如 journalctl _PID=主进程PID。对于完全没有日志输出的情况,需要回到 status 检查 ExecStart 是否真的被执行。
手动执行启动命令,把错误逼到前台
当 systemd 捕获的日志不够直观时,手动以服务相同用户运行启动命令,能直接在前台看到程序报错。先通过 systemctl cat 查看 ExecStart 和 User= 等参数,然后在 shell 里用 sudo -u 执行。注意保留环境变量,例如使用 systemctl show -p Environment 获取 Environment= 内容,必要时用 env 命令导入。手动执行只适用于不依赖 systemd 特殊资源(如套接字激活、文件描述符传递)的程序,对于使用 Type=notify 的服务,手动运行会因缺少 sd_notify 调用而超时,不能作为最终判断依据。
手动执行时要注意三点:第一,从 systemctl cat 里看到的 ExecStart 可能包含引号、变量和重定向,复制到 shell 里运行时环境未必一致,建议先执行 systemctl show -p Environment 确认环境变量。第二,如果程序需要读特定 WorkingDirectory,手动执行前没有进入该目录,可能报路径错误。第三,如果程序本身是守护进程,手动执行会停留在前台,这时按 Ctrl+C 结束,观察输出即可。
检查单元文件与配置语法
如果日志和手动执行都没有暴露问题,就要怀疑单元文件本身。执行 systemd-analyze verify 可以检查配置语法问题,它会报告未知指令、缺少部分路径等错误。此外,确认 ExecStart 里的路径是否真实存在且具备执行权限,默认路径是否被修改过。如果服务脚本有运行时依赖,比如需要临时目录或网络等待,可以在单元文件中设置 TimeoutStartSec= 和 ExecStartPre= 来诊断。常见坑是 ExecStart 写成了绝对路径但文件带了特殊字符,或引号不匹配,这类问题在日志中往往没有明确提示,只能靠静态检查发现。
这里还有一个容易忽略的点:systemd 对单元文件中的路径要求绝对路径,且不会自动展开 ~。如果路径中含空格,需要用引号包裹,但引号本身也会被 systemd 解析,所以最好的办法是用 systemd-analyze verify 输出错误信息。另外,权限问题也经常出现,比如 ExecStart 指向的文件有执行权限,但运行服务的用户没有中间目录的执行权限,这时 status 可能只显示 Permission denied。
资源限制与内核日志
部分启动失败源于系统资源限制而非程序本身。当 systemctl status 显示进程被 signal 终止,或 journalctl 中出现 out of memory 等关键词,需要检查内存、文件句柄和进程数限制。用 systemctl show -p LimitNOFILE -p LimitNPROC 查看单元文件设定的限制,再用 systemd-cgtop 观察 cgroup 资源占用。如果程序需要访问设备或挂载点,还要用 dmesg 查看内核日志,特别是 SELinux 或 AppArmor 的拒绝信息。这类日志不经过 journald,用 journalctl -k 也能看到内核环缓冲消息。资源问题通常需要调整单元文件的 Limit 项或系统级配置文件,修改后执行 systemctl daemon-reload 再重启服务。
检查资源限制时,先区分是全局限制还是 cgroup 限制。全局限制可以通过 systemctl show 查看,cgroup 限制则需要看 /sys/fs/cgroup 下的对应条目。如果是服务启动瞬间内存不足,可以尝试临时调大 MemoryMax,但要确认是泄漏还是配置不足。文件句柄不足时,日志会报 too many open files,此时需要调整 LimitNOFILE,同时也要检查系统级 fs.file-max 是否受限。
如果以上几步都查完仍然没有头绪,可以考虑使用 systemd-run 启动临时调试实例,或者临时修改单元文件加入 ExecStartPre=/usr/bin/sleep 300 延长启动窗口,但需要尽快移除调试配置。系统日志通常能给出方向,关键在于不要凭猜测改配置。