Skip to content
Cron Cookbook

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

  1. 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.
  2. It runs early in the boot

    The job starts as soon as cron does, which can be before the network is up or every drive is mounted.
  3. Cron's environment is bare

    No login profile is read and PATH is short, so use full paths to commands and scripts.
  4. 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.
  5. Only real cron has it

    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
# /etc/cron.d/startup: system crontabs name the user
@reboot root /usr/local/bin/mount-backups.sh

When @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:

sync.service
# /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.target

Use 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:

ShortcutSame asRuns
@yearly or @annually0 0 1 1 *At 00:00 on the 1st of January.
@monthly0 0 1 * *At 00:00 on the 1st of every month.
@weekly0 0 * * 0At 00:00 on Sundays.
@daily or @midnight0 0 * * *At 00:00 every day.
@hourly0 * * * *Every hour, on the hour.

Hosted schedulers don't run @reboot

Kubernetes CronJobs accept the five schedule shortcuts above but not @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.

Need the five fields themselves? See the cron expression syntax guide.