最可靠的方式是编写 systemd 服务单元文件并显式声明依赖;/etc/rc.local 兼容性差且启动早于多数服务;crontab @reboot 环境缺失严重,需手动设置 path、home 并轮询检查网络。

systemd 服务单元文件是最可靠的方式
现代 Linux 发行版(如 Ubuntu 20.04+、CentOS 7+、Debian 10+)默认使用 systemd,直接写脚本扔进 /etc/rc.local 或改 crontab @reboot 容易失败或延迟启动——因为依赖的服务(如网络、挂载点)可能还没就绪。
正确做法是写一个 .service 文件,显式声明依赖关系和启动时机:
- 服务文件放在
/etc/systemd/system/myapp.service,内容需包含[Unit]区块里的After=network.target或After=multi-user.target,必要时加Wants=network.target -
[Service]中用Type=simple(前台运行)或Type=forking(后台 daemon),避免用Type=oneshot除非脚本真的一秒完事且不驻留 - 务必设置
User=和WorkingDirectory=,否则脚本里用相对路径或访问家目录会失败 - 启用前必须执行
sudo systemctl daemon-reload,否则systemctl enable会静默忽略新文件
/etc/rc.local 兼容性差但调试快
有些老脚本或临时验证场景下仍会用 /etc/rc.local,但它在多数 systemd 系统里默认不启用,且执行时机早于大多数服务,网络、磁盘挂载常未就绪。
若坚持用,注意三点:
- 先确认
rc-local服务是否激活:sudo systemctl is-enabled rc-local;若为disabled,需创建/etc/systemd/system/rc-local.service并启用 -
/etc/rc.local文件必须有可执行权限:sudo chmod +x /etc/rc.local,且首行必须是#!/bin/bash - 脚本内所有命令建议加完整路径(如
/usr/bin/curl而非curl),因为PATH在此阶段极简,which curl可能返回空
crontab @reboot 的隐蔽陷阱
@reboot 看似简单,但实际执行环境与用户登录后差异极大:没有 $HOME、$PATH 极窄、GUI 环境变量全无,且不等待系统服务就绪。
常见失败表现:command not found、Permission denied(因没读取 ~/.bashrc 导致 ssh key agent 未加载)、脚本卡在 ping 或 curl 上。
- 必须在 crontab 条目中显式设置环境变量,例如:
@reboot PATH=/usr/local/bin:/usr/bin:/bin HOME=/home/username /path/to/script.sh - 若脚本依赖网络,加延时(
sleep 15 && ...)只是权宜之计;更稳妥的是在脚本内轮询检查:while ! ping -c1 google.com &>/dev/null; do sleep 5; done - 日志输出别省略:
@reboot /path/to/script.sh >> /var/log/myscript.log 2>&1,否则出错完全无迹可寻
权限、路径与日志验证缺一不可
无论用哪种方式,三个环节出问题概率最高:脚本自身权限不对、路径写死成绝对路径却忘了改用户、日志没开导致问题无法复现。
- 脚本文件必须对目标用户(
root或指定User=)有执行权限:sudo chown root:root /opt/myapp/start.sh && sudo chmod 755 /opt/myapp/start.sh - 所有子进程调用的命令(如
python3、node、mysqldump)优先用绝对路径,或在 service 文件里补全Environment="PATH=/usr/local/bin:/usr/bin:/bin" - systemd 服务务必配
StandardOutput=journal和StandardError=journal(默认已有),查日志统一用sudo journalctl -u myapp.service -n 50 -f
最容易被忽略的是工作目录和用户环境隔离——同一个脚本,在终端手动运行成功,放进 systemd 就失败,八成是 cd 没写、~ 展开失效、或 .env 文件没随 WorkingDirectory 一起加载。











