Run a cron job at startup with @reboot
To run a command each time the machine boots, put @reboot in place of the five time fields in your crontab: @reboot /home/alice/bin/start.sh. Cron runs it once, when the cron service starts during boot. It works in the cron that ships with Debian, Ubuntu, Fedora and RHEL, but not in hosted schedulers.
Five things to know about @reboot
Once per boot, not per cron restart
Cron leaves a marker file in/run, which is emptied at boot, and skips@rebootjobs if it's already there. Restarting the cron service doesn't run them again.- The job starts as soon as cron does, which can be before the network is up or every drive is mounted.
- No login profile is read and
PATHis short, so use full paths to commands and scripts. Output goes nowhere you'll see
There's no terminal at boot. Redirect output to a log file, or cron tries to mail it to you.- GitHub Actions, Kubernetes, AWS EventBridge, Vercel and Jenkins don't accept @reboot.
Add a job that runs at boot
Open your crontab with crontab -e, add a line that starts with @rebootfollowed by the command, as in the example crontab, and save. The job runs as you. To run it as root, edit root's crontab with sudo crontab -e.
In the system crontab, /etc/crontab, and in files under /etc/cron.d, every line names the user to run as after the schedule:
# /etc/cron.d/startup: system crontabs name the user
@reboot root /usr/local/bin/mount-backups.shWhen @reboot runs, and when it doesn't
@rebootruns when the cron daemon starts, but only the first time after a boot. Debian and Ubuntu's cron creates /run/crond.reboot as it runs the jobs, and cronie (Fedora, RHEL, Arch) creates cron.reboot in its run directory. If the file already exists, cron skips the jobs. /run is emptied at every boot, so the jobs run once per boot, and systemctl restart crondoesn't run them again.
To test a job without rebooting, run its command by hand in a minimal shell, such as env -i /bin/sh -c 'your command', which is closer to what cron gives it than your terminal is.
Wait for the network
Cron starts early, and nothing makes it wait for the network, a mounted drive or a database. A script that downloads something, connects to a server or reads from /mnt can fail on boot and then work when you run it by hand. The simple fix is a delay in front of the command: @reboot sleep 60 && /path/to/script. A script can also wait in a loop until what it needs answers.
If the job has to start after something specific, or should restart when it crashes, a systemd unit is the better tool. It can wait for network-online.target, and systemctl enable sync.service runs it at every boot:
# /etc/systemd/system/sync.service
[Unit]
Description=Sync on boot
Wants=network-online.target
After=network-online.target
[Service]
Type=oneshot
User=alice
ExecStart=/usr/bin/python3 /home/alice/sync.py
[Install]
WantedBy=multi-user.targetUse full paths
Cron doesn't read your .bashrc or .profile. It runs commands with /bin/sh and a short PATH(Debian's cron uses /usr/bin:/bin), so node or docker in /usr/local/bin may not be found. Write full paths, or set PATH= on a line of its own at the top of the crontab. A % in the command is turned into a newline unless you escape it as \%.
Check that it ran
Cron logs each job it starts. On Debian and Ubuntu, look with journalctl -b -u cron (the -b limits it to this boot); on Fedora and RHEL the service is crond. The log shows that the command started, not whether it worked.
For that, redirect the job's output to a file with >> /path/to/log 2>&1. Otherwise cron mails the output to the crontab's owner, and on most machines that mail goes nowhere.
@daily, @hourly and the other shortcuts
Cron accepts a few more names in place of the five fields. Unlike @reboot, each is a plain schedule, in the server's time zone:
| Shortcut | Same as | Runs |
|---|---|---|
@yearly or @annually | 0 0 1 1 * | At 00:00 on the 1st of January. |
@monthly | 0 0 1 * * | At 00:00 on the 1st of every month. |
@weekly | 0 0 * * 0 | At 00:00 on Sundays. |
@daily or @midnight | 0 0 * * * | At 00:00 every day. |
@hourly | 0 * * * * | Every hour, on the hour. |
Hosted schedulers don't run @reboot
@reboot. GitHub Actions, AWS EventBridge and Vercel take only the five-field form, so write 0 0 * * * rather than @daily. Jenkins has the shortcuts but hashes their times, so its @dailyisn't midnight.