systemd 是当前唯一能让你对日志和依赖关系实现“指哪打哪”的解决方案。虽然市面上还有其它旁门左道,但要么默认处于禁用状态(比如日渐被遗忘的 rc.local),要么本就不属于开机自启的职责范围(例如 /etc/profile.d/ 那一套)。简单说,想要稳定、可控、可追踪,就必须走 systemd 这条路。

许多人在 /etc/rc.local 里写命令翻车后,第一反应是检查脚本语法、权限。但你是否想过,脚本本身也许没问题,而是 systemd 压根就没打算帮你执行它?
为什么直接往 /etc/rc.local 里写命令经常失败
别怀疑自己脚本写错了——问题根源在于 systemd 根本不想帮你运行它。即使文件存在、写好了 #!/bin/bash、也给了 chmod +x,rc-local.service 依然会处于 inactive (dead) 状态。这是设计使然,并非 bug。
/etc/rc.local在 systemd 系统里只是充当“兼容层”的摆设,必须手动启用对应的 service 才会被触发。- 它的执行环境极其简陋:
PATH只包含/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin,不会加载用户 profile,也不保证网络已经就绪。 - 如果脚本里用了
sudo、systemctl、su - user -c这类需要交互的命令,会直接卡住或无声无息地失败。 - 当脚本依赖 DNS 或远程服务时,只写
After=network.target不够,必须加上network-online.target才有保障。
用 systemd 创建 .service 文件的最小可行配置
不要照搬那些冗长的模板,只保留真正用到的字段。一个能立刻生效的最简版本如下:
[Unit] Description=My Startup Script After=network.target [Service] Type=oneshot ExecStart=/opt/myscript.sh RemainAfterExit=yes [Install] WantedBy=multi-user.target
Type=oneshot适合一次性脚本;如果要运行长期进程(比如 Python Web 服务),请改成Type=simple,同时确保进程不自行 daemonize。ExecStart=必须使用绝对路径,不能用~或$HOME这类变量。RemainAfterExit=yes能让 service 状态保持 active,方便用systemctl is-active判断是否已经执行过。- 想以非 root 用户运行?加一行
User=deploy,不要在脚本里用su绕弯子。
启用后怎么确认真的跑起来了
别急着重启,先验证一遍:
- 重载配置:
systemctl daemon-reload - 启用开机自启:
systemctl enable myscript.service - 立即启动并盯住输出:
systemctl start myscript.service && journalctl -u myscript.service -n 20 -f - 检查注册状态:
systemctl is-enabled myscript.service应返回enabled - 查看实际启动时机:
systemctl list-dependencies --after myscript.service,确认它确实在 network 之后执行
@reboot crontab 和 ln -s /etc/init.d 的适用边界
它们并非“替代方案”,而是各有明确限制的备选:
@reboot由 cron 触发,仅对指定用户生效(sudo crontab -e是 root,crontab -e是当前用户),并且不保证网络就绪——只适合纯本地、无依赖的轻量级任务。ln -s /etc/init.d/myscript /etc/rc.d/rc3.d/S99myscript这种手法仅在 SysVinit 系统(CentOS 6、Ubuntu 14.04)有效;在 systemd 下,这个链接会被直接忽略,除非你额外启用systemd-sysv-generator兼容层。/etc/profile.d/下的脚本只在用户登录 shell 时被 source,系统启动时完全不执行——这也是个常被误解的“坑”。
真正容易被忽略的是:所有方式都要求脚本本身可执行(chmod +x),并且内部调用的命令必须用绝对路径。哪怕 date 在 shell 里直接敲没问题,在开机环境中也可能因为 PATH 缺失而报 command not found。
