在 Ubuntu 20.04 及以上版本中,真正能够稳定控制开机启动时机、服务依赖关系与权限边界的方案,只有 systemd。相比之下,rc.local 和 @reboot crontab 不仅缺乏完善的依赖管理能力,还经常因为运行环境不完整,导致执行时机不可预测,最终出现“看起来没报错、实际上没执行”的情况。尤其是在依赖网络、挂载点或其他系统服务先完成初始化的场景下,更不建议把它们当作 Ubuntu 开机执行命令的正式方案。

systemd 是当前 Ubuntu(20.04+)中唯一能够可靠管理启动顺序、依赖关系和权限控制的机制,其他方式要么已经逐渐失效,要么执行行为不可控。不要用 rc.local 或用户级 crontab @reboot 去运行依赖系统级资源的命令——它们极有可能因为环境变量缺失、路径配置错误或权限不足而静默失败。
使用 systemd 服务,确保命令在网络就绪后稳定执行
这是 Ubuntu 设置开机自动执行命令最可靠的方式,尤其当你的命令依赖网络(如 curl、git clone)、挂载目录(如 /mnt/data)或指定服务(如 docker)时,更应该优先使用 systemd。
After=network.target只能表示网络栈已初始化,但并不代表 DHCP 地址已经分配完成;如果希望开机启动更稳妥,建议再加上Wants=network-online.target,并启用对应服务:sudo systemctl enable systemd-networkd-wait-online.serviceUser=建议设置为普通用户(如User=ubuntu),避免长期使用 root 权限;如果确实需要 sudo,可直接在ExecStart=中调用/usr/bin/sudo,但前提是该用户已经配置免密 sudo(可通过sudo visudo添加ubuntu ALL=(ALL) NOPASSWD: /path/to/cmd)WorkingDirectory=必须明确指定,否则脚本中的相对路径(如./config.json)会默认从根目录解析,导致文件找不到而报错- 命令本身必须写成绝对路径:
ExecStart=/usr/bin/bash /home/ubuntu/myscript.sh,不能只写成bash myscript.sh,否则在 Ubuntu 开机启动过程中很容易因为 PATH 不一致而执行失败
@reboot crontab 仅适用于纯用户态、无依赖的简单启动命令
它的本质是 cron 服务启动后扫描一次 @reboot 任务,并不是“系统开机瞬间”立即执行,而是在 cron 进程启动完成后才会触发——这通常可能比你预期晚几十秒,而且完全不会感知网络连接或磁盘挂载状态。
- 它只对当前用户有效:使用
crontab -e编辑的是当前用户的 crontab;如果任务必须以 root 身份运行,需要使用sudo crontab -e,而不是在普通用户的命令前简单加sudo - 环境变量非常精简:PATH 默认通常只有
/usr/bin:/bin,~和$HOME不会自动展开,因此所有脚本路径、配置路径都必须写成绝对路径 - 常见失败表现是:
@reboot /home/user/start.sh执行后没有任何输出直接退出 → 可通过grep CRON /var/log/syslog排查,绝大多数情况都是路径错误或执行权限不足 - 即使强行增加延迟,也并不能彻底解决问题:例如
@reboot sleep 20 && /home/user/start.sh仍然可能因为网卡尚未真正可用而失败,因此不如直接改用systemd配合network-online.target
/etc/rc.local 在新版 Ubuntu 中默认已禁用,而且不建议继续使用
从 Ubuntu 18.04 开始,rc-local service 就已经被移除,系统默认不会再自动加载这个文件。如果强行重新启用,看似方便,实际上等于绕过了 systemd 的依赖管理机制,启动竞态问题会非常明显。举个常见例子:命令已经开始读取 /proc/mounts,但此时 NFS 还没有挂载完成,那么执行结果自然很容易出错。这也是为什么在 Ubuntu 开机自动运行脚本时,不推荐继续依赖 rc.local。
- 如果仍然坚持启用,先创建并启用 service:
sudo systemctl enable rc-local;同时确保/etc/rc.local以#!/bin/sh -e开头,并在文件结尾保留exit 0 - 该文件中的所有命令都会以 root 身份执行,但它的环境变量(例如
$PATH)与交互式 shell 完全不同,因此像which python3这类命令可能什么都返回不了 - 日志排查也不方便:
rc.local的标准输出和错误输出默认会被丢弃,通常需要手动追加> /tmp/rclocal.log 2>&1才能看到具体失败原因
图形界面程序必须明确声明 DISPLAY 和桌面会话环境
如果你想在 Ubuntu 开机后自动启动 firefox、gnome-terminal,或者 Python 的 Tkinter 图形界面程序,需要注意:systemd 服务默认并不具备 GUI 会话上下文,程序往往会直接启动失败。
- 必须显式加入两行配置:
Environment=DISPLAY=:0和Environment=XAUTHORITY=/home/username/.Xauthority(其中用户名需替换为实际值) User=必须设置为当前图形登录用户,不能使用 root;否则程序通常无法访问.Xauthority文件,最终被权限限制拦截- 启动时机还需要继续后移:加入
After=graphical-session.target,否则在 X server 尚未启动完成时连接:0,程序会直接报错 - 更稳妥、更符合桌面环境逻辑的方案,其实是放弃用
systemd启动 GUI 程序,改用桌面原生自启动方式:在~/.config/autostart/中放置.desktop文件,它会天然继承图形会话环境,更适合图形应用开机自启
systemctl status your-service 和 journalctl -u your-service -n 50 几乎是排查 systemd 启动失败时最可靠的两条线索;而使用其他方法失败时,很多时候你甚至连日志位置都找不到。