云服务器上 systemd 服务起不来?从 systemctl status、journalctl 到 unit 文件的排查顺序
刚改完配置执行 systemctl restart xxx,结果报 Job for xxx.service failed because the control process exited with error code;或者服务看着启动了,几秒后又变成 failed。这类问题别急着反复重启,按下面顺序把“哪一步失败、退出码是什么、失败原因在哪个日志里”说清楚。命令在 EulerOS、CentOS 7+/8、Ubuntu 20.04+ 上通用,下文用 myapp.service 作示例,请换成实际服务名。
一、先看状态和退出码
systemctl status myapp.service --no-pager -l
重点看三处:
Active:行:failed (Result: exit-code)说明主进程非 0 退出;Result: timeout说明启动超时;Result: start-limit-hit说明短时间内重启次数太多被 systemd 拦住了。Main PID: ... (code=exited, status=203/EXEC)这类状态码,含义见第三节。- 底部最后几行日志,往往已经直接给出原因。
如果是 start-limit-hit,修完问题后要先清计数再启动:
systemctl reset-failed myapp.service
systemctl start myapp.service
二、用 journalctl 看完整日志
status 只显示最后十行,完整的要看日志:
journalctl -u myapp.service -b --no-pager -n 100 # 本次开机以来最后 100 行
journalctl -u myapp.service --since "10 min ago" # 最近 10 分钟
journalctl -u myapp.service -f # 另开终端实时跟踪,再去 start
注意:有些程序把日志写到自己的文件(比如 /var/log/myapp/、Nginx 的 error.log),journal 里只有一行退出信息,这时要去程序自己的日志里找。
三、常见退出状态码对照
| 状态 | 含义 | 常见原因 |
|---|---|---|
status=203/EXEC |
启动命令执行不了 | ExecStart 路径写错、文件不存在、没有执行权限、脚本缺 shebang、Windows 换行符 |
status=200/CHDIR |
进不了工作目录 | WorkingDirectory 不存在或无权限 |
status=217/USER |
找不到运行用户 | User= 写的用户不存在 |
status=226/NAMESPACE |
沙箱目录准备失败 | ReadWritePaths 等配置的目录不存在 |
status=1 或其他小数字 |
程序自己报错退出 | 配置文件语法错误、端口被占用、依赖连不上 |
signal=KILL |
被强制杀掉 | 内存不足被 OOM 杀,或停止超时被 systemd 杀 |
203 最常见,可以这样逐项确认:
systemctl cat myapp.service | grep ExecStart
ls -l /opt/myapp/bin/start.sh # 路径是否存在、是否有 x 权限
head -1 /opt/myapp/bin/start.sh # 是否有 #!/bin/bash 这类 shebang
file /opt/myapp/bin/start.sh # 出现 CRLF 说明是 Windows 换行,用 dos2unix 转换
四、用服务的身份和环境手动跑一遍
很多“手动能跑、systemd 跑不起来”的问题,是因为两者的用户、环境变量和工作目录不同。systemd 不会读取 ~/.bashrc,PATH 也比交互式 shell 短。
systemctl show myapp.service -p User -p WorkingDirectory -p Environment -p ExecStart
sudo -u myappuser env -i PATH=/usr/bin:/bin bash -c 'cd /opt/myapp && ./bin/start.sh'
用这种“干净环境”手动执行一次,常常就能复现报错,比如找不到 java、node,或读不到某个环境变量。解决方法是在 unit 里写绝对路径,或者用 Environment= / EnvironmentFile= 明确提供变量。
五、检查 unit 文件本身
systemctl cat myapp.service # 看实际生效的内容,包括 drop-in 覆盖
systemd-analyze verify /etc/systemd/system/myapp.service
几个高频坑:
- 改完没 reload:修改 unit 文件后必须执行
systemctl daemon-reload,否则 systemd 还在用旧配置。 - Type 和程序行为不匹配:程序会自己转入后台(fork)却写了
Type=simple,或反过来,前台程序写了Type=forking,就会出现“启动后立刻退出”或“启动超时”。前台运行的程序用simple(或exec),自己 daemonize 的用forking并配好PIDFile=。 - 启动太慢超时:Java 等启动慢的程序可适当调大
TimeoutStartSec=,但先确认是真的慢,而不是卡在连数据库这类依赖上。 - 依赖顺序:要等网络就绪的服务写
After=network-online.target和Wants=network-online.target;依赖数据库的写对应的After=。 - drop-in 覆盖:
/etc/systemd/system/myapp.service.d/*.conf里的配置会覆盖主文件,systemctl cat能看到全部来源。
六、别忘了 SELinux 和端口占用
EulerOS、CentOS 默认开启 SELinux,程序放在非标准目录(比如 /data/app)时可能被拦,但手动以 root 执行却正常:
getenforce
ausearch -m avc -ts recent | tail -20
确认是 SELinux 拦截后,应调整文件上下文(semanage fcontext + restorecon),而不是直接关闭 SELinux。
端口被占用也是 status=1 的高频原因:
ss -lntp | grep ':8080'
七、修好之后
systemctl daemon-reload
systemctl restart myapp.service
systemctl is-active myapp.service && systemctl is-enabled myapp.service
如果希望进程异常退出后自动拉起,可以在 [Service] 里加:
Restart=on-failure
RestartSec=5
不过自动重启只是兜底,根因还是要从上面的日志和退出码里找出来。
小结
排查顺序可以记成:systemctl status 看退出码,journalctl -u 看完整日志,对照状态码判断大类,再用服务身份在干净环境里手动执行一次,最后检查 unit 文件、SELinux 和端口占用。按这个顺序走,大多数“服务起不来”的问题几分钟内就能定位。
- 点赞
- 收藏
- 关注作者
评论(0)