Andrew Mercer
on this page

References: crontab.guru (build/check schedules), cronitor.io (job monitoring/alerting), Fedora: automating system tasks, DigitalOcean: how to use cron.

Install and enable

sudo dnf install crontabs           # RHEL family (installs cronie, cronie-anacron, crontabs)
sudo apt-get install cron           # Debian/Ubuntu

sudo systemctl enable --now crond   # RHEL family; service is "cron" on Debian/Ubuntu
sudo systemctl status crond

Schedule syntax

* * * * * command to run
│ │ │ │ │
│ │ │ │ └── day of week (0-7, both 0 and 7 = Sunday)
│ │ │ └──── month (1-12)
│ │ └────── day of month (1-31)
│ └──────── hour (0-23)
└────────── minute (0-59)

Per-user crontabs

crontab -e                          # edit your own
crontab -l                          # list your own
crontab -u alice -e                 # edit another user's (root only)
crontab cron.txt                    # install from a file (replaces the current crontab!)
crontab -u alice cron.txt           # same, for another user

crontab -e is always the right way to install a job. Directly copying a file into /var/spool/cron/ (or crontabs/) with the right name and ownership can work but skips validation, and cron may not notice the change until it rereads spool timestamps.

The system crontab and cron.d

/etc/crontab is the system-wide crontab; unlike per-user ones it has a username field:

SHELL=/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
MAILTO=root

# minute hour dom month dow user  command
17 *    * * *   root  cd / && run-parts --report /etc/cron.hourly
25 6    * * *   root  test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.daily )
47 6    * * 7   root  test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.weekly )
52 6    1 * *   root  test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.monthly )

Individual job files with the same syntax go in /etc/cron.d/ (handy for packages, or for keeping one job per file under version control).

A backup schedule example

# /etc/cron.d/nightly-backups
MAILTO=root

00 02 * * *  appuser  rsync -a --delete /home/appuser/Documents /mnt/backup/Documents
15 02 * * *  appuser  rsync -a --delete /home/appuser/Projects  /mnt/backup/Projects
00 04 * * *  root     rsync -a --delete /var/lib/myapp          /mnt/backup/myapp

See rsync for the flags. MAILTO needs working local mail to actually deliver; otherwise redirect output explicitly (>> /var/log/job.log 2>&1) instead of relying on cron's mail.

Newer alternative: systemd timers

On systemd distributions, a .timer unit paired with a .service unit gives dependency ordering, logging via journalctl, and OnCalendar= schedules that also understand cron-like syntax. For anything more than a couple of simple jobs it is worth learning systemd.timer(5).

Troubleshooting

  • Cron jobs run with a minimal environment: no login shell, sparse PATH. Use full paths to binaries, or source the needed profile at the top of the script.
  • Check journalctl -u crond (or -u cron) or /var/log/cron for whether a job actually fired.
  • run-parts skips scripts without the executable bit and skips names containing dots (other than a leading exclusion list), which trips people up when a script named backup.sh silently never runs from /etc/cron.daily/.