Cron and Crontab Syntax: Schedule Jobs on Linux with Examples
On this page
Cron is the Linux scheduler. A small daemon wakes up every minute, reads a set of tables called crontabs, and runs any command whose time has come. Nightly backups, log cleanups, certificate renewals and "check this every five minutes" scripts are almost always cron jobs. The syntax is compact and easy to get slightly wrong, so this guide covers both the format and the traps that make jobs silently fail.
Editing your crontab
$ crontab -e # edit your crontab in $EDITOR
$ crontab -l # list it
$ crontab -r # remove it entirely (no confirmation!)
Each user has their own crontab, and jobs run as that user. crontab -e opens a temporary copy in your editor and installs it when you save; if there is a syntax error, it tells you and offers to edit again. Root can manage another user's table with sudo crontab -u alice -e.
crontab -r sits right next to -e on the keyboard and deletes everything without asking. Keep a backup with crontab -l > ~/crontab.bak, and restore it with crontab ~/crontab.bak.If crontab -e opens an editor you do not know, set EDITOR first, for example EDITOR=nano crontab -e. The environment variables guide explains how to make that permanent.
The five time fields
Every job line has five time fields followed by the command:
# ┌───────── minute (0-59)
# │ ┌─────── hour (0-23)
# │ │ ┌───── day of month (1-31)
# │ │ │ ┌─── month (1-12 or jan-dec)
# │ │ │ │ ┌─ day of week (0-7 or sun-sat; 0 and 7 are Sunday)
# │ │ │ │ │
* * * * * command to run
Each field accepts:
*for every value.- A single number, such as
5. - A list, such as
1,15,30. - A range, such as
1-5(Monday to Friday in the last field). - A step, such as
*/10(every 10th value) or0-30/5.
Common schedules
| Schedule | Meaning |
|---|---|
* * * * * | Every minute |
*/5 * * * * | Every five minutes |
0 * * * * | At the start of every hour |
30 2 * * * | Every day at 02:30 |
0 9 * * 1-5 | 09:00 on weekdays |
0 0 * * 0 | Midnight every Sunday |
0 3 1 * * | 03:00 on the first of every month |
15 6 1 1 * | 06:15 on 1 January |
0 8-18/2 * * * | Every two hours from 08:00 to 18:00 |
Most cron implementations also accept shortcuts in place of the five fields: @hourly, @daily, @weekly, @monthly, @yearly and @reboot, which runs once when the cron daemon starts after boot.
0 0 13 * 5 runs on every 13th and on every Friday, not only on Friday the 13th. To get the "and", schedule on one field and test the other inside the command.A full line in your crontab looks like this:
30 2 * * * /home/alice/bin/backup.sh >> /home/alice/backup.log 2>&1
Why jobs work by hand but fail in cron
This is the number one cron problem, and it is almost always the environment. Cron does not read your .bashrc or .profile, runs commands with /bin/sh rather than Bash, starts in your home directory and uses a minimal PATH (on Debian and Ubuntu, just /usr/bin:/bin). A command in /usr/local/bin, a Node or Python version manager, or a Bash-only feature can all fail.
The fixes:
- Use full paths to commands and files.
command -v phptells you where a program lives. - Set variables at the top of the crontab. They apply to all the lines below:
SHELL=/bin/bash
PATH=/usr/local/bin:/usr/bin:/bin
MAILTO=""
- Put the real work in a script with a
#!/bin/bashline, make it executable, and call the script from cron. That keeps the crontab readable and lets you test the exact same thing by hand. The scripting basics guide covers writing one. cdto the right directory inside the script, or use absolute paths, because cron starts in your home directory.
To reproduce cron's bare environment at the prompt, run the script with an almost empty environment: env -i /bin/sh -c '/home/alice/bin/backup.sh'. If it fails there, it will fail in cron.
The percent sign trap
In a crontab line, an unescaped % is turned into a newline and everything after it is sent to the command as input. So this common line breaks:
0 1 * * * tar -czf /backups/site-$(date +%F).tar.gz /var/www/site
Escape each percent sign with a backslash in the crontab, or move the command into a script where no escaping is needed:
0 1 * * * tar -czf /backups/site-$(date +\%F).tar.gz /var/www/site
Logging and output
Anything a job prints is emailed to the crontab's owner, or to the address in MAILTO. On servers without working local mail, that output simply disappears, which hides errors. Redirect it to a log instead:
*/10 * * * * /home/alice/bin/sync.sh >> /home/alice/logs/sync.log 2>&1
>> appends, and 2>&1 sends errors to the same file. Use > /dev/null 2>&1 only for jobs whose output you truly never need. The redirection guide explains the syntax.
To confirm that cron ran a job at all, check the system log:
$ journalctl -u cron --since "1 hour ago" # Debian and Ubuntu
$ journalctl -u crond --since "1 hour ago" # RHEL, Fedora, Rocky
$ grep CRON /var/log/syslog
Stop jobs from overlapping
If a job scheduled every five minutes sometimes takes ten, two copies end up running at once, which can corrupt backups or double-send emails. flock from util-linux takes a lock file and skips the run if the previous one still holds it:
*/5 * * * * /usr/bin/flock -n /tmp/sync.lock /home/alice/bin/sync.sh
-n means "give up immediately if locked" instead of waiting.
System crontabs and cron directories
Besides per-user crontabs, there are system-wide ones that root manages directly:
/etc/crontaband files in/etc/cron.d/have an extra field: the user to run as, between the time fields and the command./etc/cron.hourly,cron.daily,cron.weeklyandcron.monthlyhold executable scripts that run on that schedule. On Debian-based systems the file names may only contain letters, digits, underscores and hyphens, sobackup.shis silently skipped.run-parts --test /etc/cron.dailylists which scripts would actually run.
Many modern distributions also offer systemd timers, which add logging, dependencies and catch-up runs after downtime. Cron remains simpler for a one-line job, and it is everywhere.
Quick reference
| Goal | Command or line |
|---|---|
| Edit your jobs | crontab -e |
| List your jobs | crontab -l |
| Back up | crontab -l > crontab.bak |
| Every 15 minutes | */15 * * * * cmd |
| Weekdays at 08:30 | 30 8 * * 1-5 cmd |
| At boot | @reboot cmd |
| Log output | cmd >> /path/job.log 2>&1 |
| Date in a crontab | $(date +\%F) |
| No overlapping runs | flock -n /tmp/job.lock cmd |
| Did it run? | journalctl -u cron |