自己写的服务交给 systemd 管一启动就给你这么一行Job for myapp.service failed because the control process exited with error code. See systemctl status myapp.service and journalctl -xe for details.点开 status 看最后一行是codeexited, status203/EXEC。你检查了脚本路径明明存在手动跑也正常可 systemd 就是起不来。另一种更气人的情况服务能手动起来enable也执行成功了重启之后它压根没自启。这两类故障占了 systemd 排错的一大半。下面按报错码对号入座。一、先看它死在哪一步别急着改 unit先把真正的报错挖出来systemctl status myapp-l--no-pager journalctl-umyapp-b--no-pager这两条里status203/EXEC那个数字是关键它直接告诉你死因退出状态真实含义最常见的锅203/EXEC执行不了路径写错、没执行权限、脚本首行 shebang 错217/USER用户不对User指定的用户不存在200/CHDIR工作目录不对WorkingDirectory指向的目录不存在status1/FAILURE程序自己退了程序内部报错看程序自己的日志timeout启动超时Typeforking但程序没 fork或启动太慢start request repeated too quickly反复重启被掐程序秒退Restartalways触发了启动频率限制顺手做个语法体检能提前抓出拼错的指令systemd-analyze verify /etc/systemd/system/myapp.service没输出就是语法没问题。有输出会直接指出哪一行有问题。二、对着原因改 unit 文件报 203/EXEC三个原因按这个顺序查# 1. ExecStart 里的路径写全了吗systemd 不认相对路径whichpython3 systemctlcatmyapp|grepExecStart# 2. 文件有执行权限吗ls-l/opt/myapp/start.shsudochmodx /opt/myapp/start.sh# 3. 脚本第一行的 shebang 对吗head-1/opt/myapp/start.sh有坑systemd 不会帮你展开~、$HOME这类变量也认不了相对路径。ExecStart里一律写绝对路径。有坑脚本在 Windows 上编辑过行尾是 CRLFshebang 变成#!/bin/bash\rbash 找不到直接 203。用file start.sh能看到CRLF line terminators修一下sed-is/\r$///opt/myapp/start.sh程序其实起来了但 systemd 报超时这是Type设错了。前台运行的程序用默认的simple会自己 fork 到后台的老程序要用forking[Service] Typeforking PIDFile/var/run/myapp.pid有坑Typeforking必须配PIDFile否则 systemd 找不到主进程会误判成启动失败。反过来前台程序错设成forkingsystemd 会一直等到超时。依赖的服务还没起来就抢跑[Unit] Afternetwork.target postgresql.service Wantspostgresql.serviceAfter只管顺序Wants才是真去拉起依赖。用Requires的话依赖挂了自己也跟着挂一般服务用Wants更稳。有坑Afternetwork.target只表示网络服务管理器起来了不代表网卡已经拿到 IP。要等真正联网用network-online.target并配合Wantsnetwork-online.target。程序秒退想让它自动拉起来[Service] Restarton-failure RestartSec5 StartLimitIntervalSec0Restarton-failure表示非正常退出才重启always是退出就重启包括你手动 stop 的情况慎用。有坑StartLimitIntervalSec默认 10 秒内最多启动 5 次超了就报start request repeated too quickly并且不再尝试。程序启动慢的话把它设成 0不限或者调大。需要环境变量[Service] EnvironmentJAVA_HOME/usr/lib/jvm/java-11 EnvironmentFile/etc/myapp/env.confEnvironmentFile里按KEYvalue一行一个写。三、能跑起来之后让它开机自启sudosystemctl daemon-reload# 改了 unit 文件必须跑这条sudosystemctlenable--nowmyapp# 设自启 立刻启动systemctl is-enabled myappis-enabled的几种输出含义不一样输出状态怎么办enabled已自启正常disabled未自启systemctl enable myappstatic不能自启只能被别人拉正常检查是谁 Wants 它masked被彻底屏蔽systemctl unmask myapp有坑masked状态很多人不知道怎么来的通常是之前有人执行过systemctl mask。它比disable更狠连手动 start 都不行必须unmask才解。有坑改了 unit 文件不跑daemon-reloadsystemd 用的还是内存里的旧版本。你改半天没效果八成卡在这一步。四、enable 成功但重启后没起来查这三处1. WantedBy 跟当前 target 对不上systemctl get-default systemctlcatmyapp|grepWantedBy服务器版默认multi-user.target桌面版默认graphical.target。unit 里写WantedBygraphical.target而机器跑在multi-user.target上就不会自启。最稳的写法是[Install] WantedBymulti-user.targetmulti-user.target在桌面版上同样会被拉起。2. 服务是 socket 激活的有些服务没有 service 在跑靠.socket单元守着端口来请求才拉起systemctl list-units--typesocket--all|grepmyapp这种情况systemctl status myapp.service显示 inactive 是正常的看 socket 单元就行。3. 开机太早依赖的东西还没就绪启动时磁盘还没挂载完、网络还没通。加上[Unit] Afternetwork-online.target remote-fs.target Wantsnetwork-online.target想看服务是不是拖慢了开机用这条systemd-analyze blame|head-20五、防复发unit 文件交付前过一遍清单写完 unit 别直接上生产对着这份清单过一遍cat~/checkunit.shEOF #!/bin/bash U$1 echo 语法体检 systemd-analyze verify /etc/systemd/system/$U 21 | head -10 echo ExecStart 是否绝对路径 systemctl cat $U | grep -E ^ExecStart | grep -v ^ExecStart/ echo 有相对路径改掉 || echo OK echo 执行权限 P$(systemctl cat $U | grep -m1 ^ExecStart | sed s/ExecStart// | awk {print $1}) [ -x $P ] echo OK: $P 可执行 || echo 有问题: $P 不可执行或不存在 echo WorkingDirectory 是否存在 W$(systemctl cat $U | grep -m1 ^WorkingDirectory | cut -d -f2) [ -z $W ] || { [ -d $W ] echo OK: $W || echo 有问题: $W 不存在; } echo 自启状态 systemctl is-enabled $U 21 EOFchmodx ~/checkunit.sh用法./checkunit.sh myapp.service。还有一条经验别直接改/usr/lib/systemd/system/里的 unit。那个目录是软件包装的系统升级会被覆盖。要改就复制到/etc/systemd/system/再改/etc下优先级最高。附unit 文件结构与指令速查unit 三段结构[Unit] Description服务说明 Afternetwork.target # 启动顺序在谁之后 Wantspostgresql.service # 弱依赖会去拉起对方挂了不影响自己 Requirespostgresql.service # 强依赖对方挂了自己也挂 [Service] Typesimple # simple(前台) / forking(后台,需PIDFile) / oneshot / notify Userappuser WorkingDirectory/opt/myapp EnvironmentKEYvalue EnvironmentFile/etc/myapp/env.conf ExecStart/opt/myapp/start.sh ExecReload/bin/kill -HUP $MAINPID Restarton-failure # no/on-success/on-failure/on-abnormal/always RestartSec5 StartLimitIntervalSec0 [Install] WantedBymulti-user.target # 服务器版与桌面版通用的自启目标常用指令systemctl daemon-reload 改完 unit 必跑 systemctl enable --now 服务 设自启并立刻启动 systemctl disable --now 服务 取消自启并停止 systemctl is-enabled 服务 查自启状态 systemctl mask / unmask 服务 彻底屏蔽 / 解除屏蔽 systemctl cat 服务 看最终生效的 unit 内容 systemd-analyze verify 文件 unit 语法体检 systemd-analyze blame 看谁拖慢了开机 journalctl -u 服务 -b 看本次启动的日志systemd 排错其实有固定套路看status后面的退出码按码找原因改完daemon-reload。大部分人卡住是因为只盯着Job failed那句提示没往下看状态码。