Sometimes a Linux machine needs to run a command or script when it starts: rebuilding a cache, starting a small worker, or preparing an application. Cron can schedule recurring jobs, but boot-time execution needs a different trigger.
Historically, two common choices were /etc/rc.local and cron's @reboot extension. Both are covered below, along with the systemd service approach that is usually the better default on current Linux distributions.
1. Using /etc/rc.local on legacy systems
This is a legacy method, not the recommended default for a new deployment. Some distributions provide an rc-local compatibility service, but others do not ship or enable it. Older RHEL/CentOS setups may use /etc/rc.d/rc.local instead of /etc/rc.local.
First confirm that your system actually supports it:
systemctl status rc-local.service
If the compatibility service exists, open the file:
sudo vi /etc/rc.local
Add your command before any final exit 0 line. Use absolute paths because the boot environment is not your interactive shell:
/bin/sh /home/clinerds/script.sh
You can locate a command's absolute path with command -v, for example:
command -v date
If you want to execute the script directly rather than through /bin/sh, make it executable first:
chmod +x /home/clinerds/script.sh
Invoking it as /bin/sh /home/clinerds/script.sh does not require the executable bit. Keep this method only where an existing system already relies on it; do not add a compatibility mechanism to a modern host merely to avoid creating a service.
2. Using cron @reboot
Many, but not all, cron implementations support @reboot. Edit the current user's crontab:
crontab -e
Then add an explicit environment and redirect both output streams to a log:
SHELL=/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
@reboot /bin/sh /home/clinerds/script.sh >> /home/clinerds/reboot-task.log 2>&1
The job runs as that user, so the script, its inputs, and the log directory must be accessible to that account. Use sudo crontab -e only when root privileges are genuinely required.
The original delayed form is still valid where supported:
@reboot (sleep 120; /bin/sh /home/clinerds/script.sh) >> /home/clinerds/reboot-task.log 2>&1
A sleep only guesses that dependencies will be ready after 120 seconds; it does not check them. Use systemd dependencies or application-level retries instead when readiness matters.
3. Why systemd is usually better
On systemd-based systems, a service provides explicit boot ordering, a chosen user and working directory, reliable logs, and straightforward enablement. You can inspect a failed run instead of wondering whether a boot script executed.
For example, create /etc/systemd/system/myapp-startup.service to rebuild an application cache after the network is online:
[Unit]
Description=Rebuild MyApp cache at boot
Wants=network-online.target
After=network-online.target
[Service]
Type=oneshot
User=myapp
WorkingDirectory=/opt/myapp
Environment=APP_ENV=production
ExecStart=/usr/bin/python3 /opt/myapp/jobs/rebuild_cache.py
[Install]
WantedBy=multi-user.target
Adjust the paths and account for your system; command -v python3 shows the interpreter path. Type=oneshot suits a command that finishes. Omit the network directives if the task is local, and remember that network-online.target does not guarantee that an external API is reachable.
Environment and working-directory differences
Boot jobs do not inherit your SSH session, aliases, shell profile, or current directory. Cron normally supplies a small environment, while a system service defaults to / as its working directory and root as its user unless configured otherwise. Set WorkingDirectory=, User=, and any required Environment= values explicitly. With cron, use cd /path/to/project && ... when a script expects relative paths.
Enable the service and read its logs
After creating or editing the unit, validate it and reload systemd:
sudo systemd-analyze verify /etc/systemd/system/myapp-startup.service
sudo systemctl daemon-reload
sudo systemctl start myapp-startup.service
sudo systemctl status myapp-startup.service
Starting the service tests the command immediately. Standard output and errors go to the journal by default:
sudo journalctl -u myapp-startup.service -b
If the result is correct, enable it for future boots:
sudo systemctl enable myapp-startup.service
Run daemon-reload after every later unit edit. A one-shot service may be inactive after completing successfully; its journal and process exit status tell you whether it worked.
4. Test with an actual reboot
A manual start does not test boot ordering or enablement. During a maintenance window, reboot the machine:
sudo systemctl reboot
After signing back in, check enablement, the current boot's logs, and the real-world result of the task:
systemctl is-enabled myapp-startup.service
journalctl -u myapp-startup.service -b
For cron, inspect reboot-task.log and confirm that the cron daemon (cron or crond, depending on the distribution) is running. Avoid testing only by restarting the daemon: on some cron implementations, @reboot jobs also run when cron restarts.
Troubleshooting and security notes
- It works interactively but not at boot: check absolute paths, permissions, environment variables, working directory, mounted filesystems, and dependency readiness.
- There is no useful output: add cron redirection or read the systemd journal. Do not rely on a terminal or email being available.
-
The service does not run automatically: confirm the
[Install]section,WantedBy=multi-user.target, andsystemctl is-enabledoutput. -
The task runs twice: remove the old cron or
rc.localentry after migrating to systemd. - Avoid unnecessary root access: root-executed units and scripts must not be writable by untrusted users. Protect application configuration and credentials with appropriate ownership and permissions.
Top comments (0)