How a cron expression is structured
Cron is the job scheduler on every Linux server. Each line of a crontab consists of five time fields followed by the command. A field containing * means "every value". The fields are read from left to right: minute, hour, day of month, month, day of week.
| Field | Allowed values | Example | Meaning |
|---|---|---|---|
| Minute | 0 to 59 | 30 | at minute 30 |
| Hour | 0 to 23 | 3 | at 3 a.m. |
| Day of month | 1 to 31 | 1,15 | on the 1st and 15th |
| Month | 1 to 12 or JAN to DEC | */3 | January, April, July, October |
| Day of week | 0 to 7 or SUN to SAT | 1-5 | Monday through Friday |
In the day of week field, both 0 and 7 mean Sunday. Names such as MON or JAN are case insensitive.
Special characters
| Character | Meaning | Example |
|---|---|---|
* | every value of the field | * * * * * runs every minute |
, | list of individual values | 0,30 at minute 0 and 30 |
- | range, both ends included | 9-17 from 9:00 to 17:00 |
/ | step value after * or a range | */15 every 15 minutes, 8-18/2 every second hour from 8:00 to 18:00 |
@reboot | once when the cron service starts | @reboot /usr/local/bin/start.sh |
@hourly, @daily, @weekly, @monthly, @yearly | shortcuts for 0 * * * *, 0 0 * * *, 0 0 * * 0, 0 0 1 * *, 0 0 1 1 * | @daily /root/backup.sh |
A step after a single number such as 5/10 is accepted by some generators, but cron on Debian and Ubuntu rejects it. Write 5-59/10 instead.
Day of month and day of week together: OR, not AND
The most common surprise: if both the day of month and the day of week are restricted, the job runs when either condition is true. So 30 2 13 * 5 does not mean "Friday the 13th" but "on the 13th of every month and additionally every Friday". If one of the two fields contains a *, only the other one counts. If you really want Friday the 13th only, check the day inside the command itself:
30 2 13 * * [ "$(date +\%u)" = 5 ] && /root/friday13.shThe generator above calculates the next run times exactly according to this rule, the same way cron on Debian evaluates it.
Pitfalls that cost time in practice
PATH is almost empty
Cron starts commands with a minimal environment, usually just PATH=/usr/bin:/bin. What works in your shell fails in the cron job with "command not found". Use absolute paths (/usr/bin/php, /usr/local/bin/wp) or add your own line PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin at the top of the crontab. command -v php shows the path of a program.
The percent sign
In a crontab, % is a special character: it ends the command, and everything after it is passed as standard input. So date +%F has to be written as date +\%F. The generator above escapes percent signs in the command automatically.
Server time zone
Cron uses the server's time zone, not yours. The generator above shows the run times in your browser's time zone. timedatectl shows the server time zone. When daylight saving time starts in spring, the hour from 2:00 to 3:00 is skipped. Cron on Debian then runs jobs scheduled for 2:30 right away, and in the fall they run only once despite the repeated hour. Still, it is better to schedule important jobs outside this hour.
Output and MAILTO
Cron emails everything a job prints to the owner of the crontab. Without working local mail delivery the output disappears, with working mail delivery a mailbox may fill up. Control this deliberately: MAILTO="admin@example.com" at the top of the crontab for error mails, MAILTO="" to turn mail off, or redirect the output to a log file with >> /var/log/job.log 2>&1. > /dev/null 2>&1 discards everything, including errors. That is convenient, but you will not notice when the job fails.
Overlapping runs
If a job takes longer than its interval, cron starts it again anyway. With backups or imports this leads to double load or corrupted data. flock -n /tmp/job.lock command only starts the command if no other instance holds the lock. The option above adds this for you.
Plesk: create tasks in the panel
In Plesk you create cron jobs for a subscription under Websites & Domains, Scheduled Tasks. Plesk writes them into the crontab of the subscription's system user. If you edit the same crontab in parallel with crontab -e -u user, the panel and the file can drift apart. So stick to one method. For subscriptions with a chrooted shell, the cron job can only use programs available inside the chroot environment. Call PHP there through the Plesk PHP version, for example /opt/plesk/php/8.3/bin/php.
Examples
| Expression | Meaning |
|---|---|
*/5 * * * * | every 5 minutes, for example for WordPress tasks via wp cron event run --due-now |
*/15 9-17 * * 1-5 | every 15 minutes between 9:00 and 17:59, Monday through Friday |
0 3 * * * | daily at 3:00, the classic time for backups |
30 4 * * 0 | Sundays at 4:30, for example for log rotation or updates |
0 0 1,15 * * | on the 1st and 15th of every month at midnight |
0 */6 * * * | every 6 hours: 0:00, 6:00, 12:00 and 18:00 |
0 2 1 */3 * | quarterly on the 1st at 2:00 (January, April, July, October) |
@reboot | once after every server restart |
crontab -l lists a user's existing jobs, the system-wide ones live in /etc/crontab and /etc/cron.d/. Whether a job has run is revealed by grep CRON /var/log/syslog or, on systems without syslog, journalctl -u cron.