Cron Syntax: The Complete Reference to the Five Fields and Common Examples

A precise reference to cron syntax: the meaning of each of the five fields, special expressions like */15 and @daily, and a table of common schedules you can use directly in crontab.

7 min Updated 20 Sep 2026

If you've opened crontab -e and don't understand why your task didn't run at 3 AM, or why a script runs every minute instead of every hour, the problem is almost always the field order. Cron syntax has five fields, and their order is fixed; swapping two fields makes the schedule mean something else without any error. Cron won't tell you anything. It just won't run.

The five fields of cron syntax and the allowed range of each

Each line in crontab reads these five fields from left to right, then the command:

minute  hour  day of month  month  day of week  command
  0      3        *            *       *         /usr/bin/php /var/www/backup.php
FieldRangeMeaning
Minute0–59Minute of the hour
Hour0–23Hour of the day, midnight is zero
Day of month1–31Calendar day
Month1–12January is one, not zero
Day of week0–7Zero and seven are both Sunday

Two points that cause the most errors: the month starts at one but the day of week starts at zero, and the number 7 is also Sunday. If someone is used to counting months from zero from another language, their schedule shifts by one month and they won't notice until the end of the year.

Operators and special expressions in cron syntax

Each field can take one of these four forms:

  • Asterisk (*): any allowed value. Meaning "every minute" or "every hour."
  • List (1,15,30): several specific values, separated by commas.
  • Range (9-17): from the first value to the last, inclusive of both ends.
  • Step (*/15 or 10-50/5): every n units. */15 in the minute field means 0, 15, 30, and 45.

Combining a step with a range is where many people make mistakes. The expression 10-50/5 in the minute field means start at minute 10 and advance every 5 minutes up to 50; the result is 10, 15, 20, and so on. But 50/5 without a range means from 50 to 59, not "every five minutes across the whole hour." If you want it to run every five minutes throughout the entire hour, write */5.

Month and day-of-week names

Instead of a number, you can write the first three letters of the English name: mon, tue, jan, dec. Uppercase and lowercase don't matter. Ranges work too: mon-fri means the weekdays.

Shorthand expressions

Cron has eight macros that replace all five fields:

ExpressionEquivalent
@rebootOnce, when the system boots up
@yearly / @annually0 0 1 1 *
@monthly0 0 1 * *
@weekly0 0 * * 0
@daily / @midnight0 0 * * *
@hourly0 * * * *

These expressions aren't supported in older versions of cron. If you're working on an old system and @daily is silently ignored, write the numeric equivalent.

Common scheduling examples

Cron syntaxRun time
*/5 * * * *Every 5 minutes
0 * * * *At the start of every hour
30 2 * * *Every day at 2:30 AM
0 3 * * 0Sundays at 3 AM
0 0 1 * *The first of every month, at midnight
0 9-17 * * 1-5Every hour, 9 to 17, Monday to Friday
15 4 1,15 * *On the 1st and 15th of every month, at 4:15
0 */6 * * *Every 6 hours

For heavy jobs like database backups, 2 to 4 AM is the right choice because site traffic is low. But if you're on shared hosting, this hour is the server's busiest time; everyone has put their backups there. 1:20 or 4:40 is a better choice. To see how much resources each task consumes, the complete reference to hosting resource limits explains what each number counts.

This is where they go wrong

The most common mistake I see in tickets is this: the user schedules a PHP script with 0 3 * * * php /home/user/backup.php, the task runs, but nothing happens. The telltale sign is that in /var/log/syslog you see the line CRON[12345]: (user) CMD (php /home/user/backup.php), meaning cron ran the command, but the output is empty. The cause is almost always PATH. Cron runs with a minimal environment; php isn't in its PATH or is a different version. The solution: give an absolute path, /usr/bin/php8.2, and put a PATH=/usr/local/bin:/usr/bin:/bin at the top of crontab.

The second mistake, which is harder to find: the script's output goes nowhere. Cron sends any output as email to the local user. If no MTA is installed, the message stays in the queue and after a while /var/mail fills up. Always put >> /var/log/backup.log 2>&1 at the end of the command. If you deliberately want no output, use >/dev/null 2>&1.

A point you won't find in the docs

When both the day of month and the day of week have specific values, cron combines them with OR, not AND. That is, 0 0 1 * mon means "the first of every month, or every Monday" — not "the first of the month only if it's a Monday." This behavior is standard in POSIX and has been preserved in most implementations. If you really want "the first of the month that falls on a Monday," you have to check inside the script itself or use a systemd timer with OnCalendar=Mon *-*-01.

Second point: cron is unaware of DST. If the server is set to local time and the clock goes back, tasks between those hours run twice. On production servers, always run timedatectl set-timezone UTC and write task times in UTC. This also makes it possible to compare logs between servers.

For long-running jobs, cron is the wrong choice. If a task takes longer than its run interval, two instances run simultaneously and lock up the database. The simple solution: flock. Write the command like this:

*/10 * * * * /usr/bin/flock -n /tmp/sync.lock /usr/bin/php /var/www/sync.php

The -n switch means if the lock is taken, don't run and exit. Without it, flock waits and the task queue piles up.

Cron or systemd timer?

If you're working on a dedicated server or VPS with systemd and need structured logging, dependency on other services, or resource control, a systemd timer is the better choice. Every run is recorded in journald, you can set RandomizedDelaySec so all tasks don't start at the same time, and with Persistent=true missed tasks are made up after a restart. Cron has none of these.

But for 90 percent of everyday jobs on Linux hosting — clearing temp files, running WordPress cron, log rotation — cron is simpler and sufficient. You learn the syntax once and it works everywhere. Personally, I don't go for timers until I need structured logging or service dependencies.

One practical limitation you'll hit on shared hosting: the minimum run interval is usually 5 or 15 minutes, and per-minute tasks are blocked. If you really need per-minute execution, change the architecture: write a daemon or use a queue worker. To see diagnostic and testing tools, the free webmaster tools are a good starting point.

Frequently asked questions

How do I know whether cron ran my task or not?

On systems with systemd, the command journalctl -u cron --since "1 hour ago" shows every run. On older systems, grep CRON /var/log/syslog does the job. If you see the CMD line but there's no effect, the problem is in the execution environment or the script itself, not the schedule.

Why doesn't my cron task work with the correct PATH?

Cron runs with a minimal environment and doesn't read your shell variables. Define the PATH value explicitly at the top of crontab and use the absolute path of the binary in the command. The same applies to environment variables like DATABASE_URL; define them in crontab or use a wrapper script.

What's the difference between */5 and 0-59/5 in cron syntax?

There's no difference. Both mean every five minutes across the whole hour. The difference appears when you limit the range: 10-50/5 only covers minutes 10 to 50 and skips minutes 55, 0, and 5.

Can I run cron on multiple servers at the same time without the task running twice?

Not with plain cron. Cron has no distributed locking mechanism. If you have multiple servers behind one database, you have to implement the lock at the database layer or with a tool like Redis. The simpler way: schedule the task on only one server and have the rest use its result.

If you have a broken crontab right now, first look at the current contents with crontab -l, then fix it with crontab -e. Before saving, count the five fields again from left to right. This one habit alone eliminates most "cron doesn't work" tickets.

Was this page helpful?