启动Linux服务后看到 code=exited, status=203/EXEC,说明systemd已经走到启动进程这一步,但内核没有成功执行目标程序。systemd文档把203定义为 EXIT_EXEC,常见原因是可执行文件缺失或当前服务上下文无法访问。
203/EXEC发生在程序真正运行之前,先查ExecStart和执行环境,不要直接调应用参数。 同一条命令在交互式Shell里能运行,也不代表它在服务用户和systemd隔离设置下可执行。

读取systemd实际加载的配置
先查看状态、最近日志和所有合并后的单元片段:
sudo systemctl status example.service --no-pager -l
sudo journalctl -u example.service -b -n 100 --no-pager
sudo systemctl cat example.service
再读取systemd解析后的关键属性:
sudo systemctl show example.service \
-p FragmentPath -p DropInPaths -p ExecStart \
-p User -p Group -p WorkingDirectory \
-p RootDirectory -p RootImage
管理员经常只编辑 /etc/systemd/system 中的主文件,却忽略drop-in覆盖项,或者改完后没有重新加载。应以 @@HTMLTOKEN1@@ 和 @@HTMLTOKEN2@@ 的结果为准,而不是凭记忆判断ExecStart。
核对路径、权限和服务用户
假设ExecStart指向 /opt/example/bin/server,逐级检查路径:
namei -l /opt/example/bin/server
stat /opt/example/bin/server
file /opt/example/bin/server
然后用单元配置的用户验证可访问性:
sudo -u example test -x /opt/example/bin/server
printf 'exit=%s\n' "$?"
文件本身需要执行权限,服务用户还必须能够穿过每一级父目录。若路径位于带 noexec 选项的文件系统,即使权限位包含x也可能无法执行:
findmnt -T /opt/example/bin/server -o TARGET,FSTYPE,OPTIONS
不要为了验证就对整个目录执行 @@HTMLTOKEN0@@。 应只修正明确缺失的属主、目录遍历权限或文件执行位。
脚本还要检查解释器和换行符
ExecStart如果指向脚本,首行shebang所写的解释器必须存在且可执行:
head -n 1 /opt/example/bin/start.sh
file /opt/example/bin/start.sh
例如脚本首行为 #!/usr/bin/python3,但系统实际只安装了其他路径的Python,内核会把它视为无法执行。Windows的CRLF换行还可能让解释器路径末尾混入不可见字符,可用下面命令检查:
sed -n '1l' /opt/example/bin/start.sh
systemd不会默认通过交互式Shell解释 ExecStart= 中的管道、重定向或环境变量展开。确实需要Shell语法时,应显式使用受控的 /bin/sh -c,并避免把不可信输入拼入命令。能直接执行程序及参数时,不要额外套Shell。
检查架构、动态加载器和隔离设置
二进制架构与VPS不匹配、动态加载器缺失或库路径异常,也会导致内核无法完成执行:
uname -m
file /opt/example/bin/server
readelf -l /opt/example/bin/server | grep 'interpreter'
ldd /opt/example/bin/server
ldd 不应对来源不明的二进制随意运行;对不可信文件优先使用 readelf。如果单元设置了 RootDirectory= 或 RootImage=,程序和解释器必须在那个根环境中可见;ProtectSystem=、InaccessiblePaths= 等沙箱项也可能改变访问范围。
SELinux或AppArmor拒绝执行时,应查看对应审计日志并修正规则或文件标签,不要全局关闭强制访问控制来绕过203/EXEC。
需要安全复现单元文件时,可在 萤光云 建立隔离测试机;要对比不同发行版的systemd行为,也可以用 LightNode 部署临时环境。只复制服务配置与无敏感数据的测试程序。
修改后重新加载并启动
先让systemd检查单元文件,再重新加载和启动:
sudo systemd-analyze verify /etc/systemd/system/example.service
sudo systemctl daemon-reload
sudo systemctl restart example.service
如果单元由软件包管理器提供,优先使用drop-in:
sudo systemctl edit example.service
这样可以减少升级时覆盖本地修改的风险。修改执行用户、路径或沙箱设置前应保留原配置和回退命令,生产服务重启要在维护窗口内进行。
验收服务确实进入运行状态
启动成功后同时确认systemd状态、主进程、监听端口和业务健康检查:
sudo systemctl is-active example.service
sudo systemctl show example.service -p MainPID -p ExecMainStatus
sudo journalctl -u example.service -b -n 50 --no-pager
active 只表示单元状态符合定义,仍需按服务类型检查端口、套接字或HTTP健康端点。对于 Type=oneshot,进程正常退出后显示active或inactive可能是预期行为,应结合单元语义判断。
验收通过应满足203/EXEC不再出现、主程序由正确用户启动、日志无权限或解释器错误,并且真实业务探测正常。
FAQ
ExecStart写相对路径可以吗?
优先写绝对路径。部分systemd版本允许不含斜杠的简单程序名并按固定搜索路径查找,但不能依赖交互式Shell的PATH和别名。
chmod +x之后还需要daemon-reload吗?
只改文件权限通常不需要重新加载单元;如果修改了service文件或drop-in,则必须执行 systemctl daemon-reload。
203/EXEC和应用退出码1有什么区别?
203表示systemd未能成功执行进程;退出码1通常表示程序已经启动并由程序自身返回失败。
温馨提示
203/EXEC的关键是还原systemd看到的执行环境。先读取生效配置,再按路径、权限、解释器、挂载和隔离层逐项验证;不要用777权限或关闭安全模块换取临时启动。


