摘要:一次 x86 Linux 可执行文件开机自启动的踩坑复盘。目标程序需要 root 跑、需要 X 显示、但机器又不接显示器,GDM 还反复崩溃。最后靠 Xvfb + systemd + rc.local 分离搞定,过程可复制。很多做 Linux 服务器、工控部署、边缘计算的同学,都会遇到这种场景:
一台 x86 架构的 Linux 机器,某个可执行文件需要 root 权限、开机自动启动、长期稳定运行。
如果是普通后台服务,写个 systemd 服务就结束了。但现实中往往没那么顺利:
qt.qpa.xcb: could not connect to display :0;这篇文章就是一次完整复盘:从踩坑到最终方案,不依赖真实显示器、不依赖 GDM 登录,最终实现稳定自启动。
Qt、Electron、Java Swing/AWT 这类程序,哪怕只有一个最小窗口,也需要一个 X11 显示环境。直接用 systemd 启动,会报错:
qt.qpa.xcb: could not connect to display :0普通后台服务没有 DISPLAY,程序自然跑不起来。
常见思路是:让系统登录到某个用户,自动产生一个 :0,然后程序连上去。
结果 GDM 在那台机器上反复崩溃,日志大概长这样:
GdmDisplay: Session never registered, failinggdm.service: Main process exited, code=dumped, status=11/SEGV显示会话稳不住,程序当然也起不来。
即使 DISPLAY 解决了,也可能出现这种错误:
qt.qpa.plugin: Could not load the Qt platform plugin ”xcb”原因通常是 QTDIR、LD_LIBRARY_PATH、QT_PLUGIN_PATH 指向了不存在的路径,或者平台插件依赖缺失。
核心思路一句话:
不依赖真实显示器,也不依赖桌面登录,用轻量虚拟显示 Xvfb 给程序提供 DISPLAY。
同时把不同职责的启动任务拆开:
:0 | ||
这样做的好处:
CentOS / RHEL:
yum install -y xorg-x11-server-XvfbDebian / Ubuntu:
apt-get install -y xvfb脚本逻辑:先启动 Xvfb,设置 Qt 环境变量,杀掉旧进程,再启动目标程序,最后循环守护。
#!/bin/sh# 设置 X 显示和 Qt 环境export DISPLAY=:0export QTDIR=/opt/Qt/5.15.2/gcc_64export LD_LIBRARY_PATH=/opt/Qt/5.15.2/gcc_64/lib:/your/app/libexport QT_PLUGIN_PATH=/opt/Qt/5.15.2/gcc_64/plugins# 启动虚拟显示(如果还没运行)if ! pgrep -x "Xvfb" > /dev/null 2>&1; thenXvfb :0 -screen 0 1920x1080x24 -ac \+extension GLX +render -noreset \> /var/log/Xvfb.log 2>&1 &sleep 2fi# 启动你的可执行文件/your/app/your_executable > /var/log/your_app.log 2>&1 &PID=$!# 守护循环:程序挂了就让 systemd 重启while kill -0 $PID 2>/dev/null; dosleep 5doneecho "your_executable exited"exit 1
注意:把 /your/app/your_executable 和 Qt 路径替换成你自己的实际路径。
#!/bin/shPIDS=$(ps aux | grep '/your/app/your_executable' | grep -v grep | awk '{print $2}')for PID in $PIDS; dokill -15 $PID 2>/dev/null || truedonesleep 2for PID in $PIDS; doif kill -0 $PID 2>/dev/null; thenkill -9 $PID 2>/dev/null || truefidone
[Unit]Description=Your App ServiceAfter=network.target[Service]Type=simpleWorkingDirectory=/your/appExecStart=/your/app/start.shExecStop=/your/app/stop.shRestart=alwaysRestartSec=5User=root[Install]WantedBy=multi-user.target
提示:Restart=always会无论什么退出原因都自动拉起;如果希望程序主动退出时不再重启,可以替换为Restart=on‑failure。

保存到 /etc/systemd/system/your-app.service,然后启用:
systemctl daemon-reloadsystemctl enable your-app.servicesystemctl start your-app.service
如果你还有其他脚本需要开机跑,比如配置下发、日志清理、健康检查,可以放到 rc.local:
#!/bin/bashtouch /var/lock/subsys/local# 启动配套服务cd /your/other/service && ./XX.sh start &exit 0
然后给权限并启用:
chmod +x /etc/rc.d/rc.localsystemctl enable rc-localsystemctl start rc-local
# 查看主服务状态systemctl status your-app.service# 查看相关进程ps aux | grep -E ”Xvfb|your_executable” | grep -v grep# 查看端口监听ss -tlnp | grep your_port
如果看到 Xvfb :0 和目标程序都在运行,对应端口也在监听,基本就稳了。
更彻底一点,执行一次重启:
reboot重启后等 1-2 分钟,再检查上面三项,确认是开机自动启动而不是人工启动的。
服务器、工控机、远程机柜很多都没有接显示器,Xvfb 完美解决了这个限制。
桌面环境登录失败、卡死、崩溃的情况太常见,直接绕过它,系统更稳定。
systemd 服务里可以明确指定 User=root,需要 root 才能操作的硬件资源(网卡、CAN 口、串口等)都能正常访问。
显示程序、配套服务、系统配置分开管理,维护、排查、重启都互不干扰。
一句话总结:只要你的程序离不开 DISPLAY,但机器又不接显示器,这套方案基本都能用。
部署前按这个清单检查一遍,能少踩很多坑:
Xvfb
DISPLAY=:0
QTDIR、LD_LIBRARY_PATH、QT_PLUGIN_PATHstart.sh
stop.sh 有执行权限enable并 start/etc/rc.d/rc.local
rc-local 已启用/var/log/ 方便排查reboot验证开机自启动
往期推荐TCP客户端 vs 服务端:设备接入测试的3个踩坑和1个工具箱经验
OpenCode 读不了 Word/PDF/Excel?我是这么解决的
如果这篇对你有帮助,建议 收藏 + 转发,下次遇到 Linux 程序开机起不来的问题,可以直接按这个思路排查。