Skip to content

cron Basics

A server that depends on someone remembering to run a command every night eventually fails, because people forget, go on leave, or are asleep at 3 a.m. when a log directory fills the disk. Linux solves this with cron, a background service that has scheduled commands on Unix-family systems since the 1970s. Once a job is registered with cron, it runs at the exact time specified, every time, without anyone logged in.

What You Will Learn

By the end of this article you will be able to:

  • explain what the cron daemon is and how it decides when to run a job;
  • read and write the five-field time specification used in a crontab line;
  • use *, lists, ranges, and step values (*/N) in each field;
  • use the @reboot, @daily, and similar shortcuts correctly;
  • avoid the classic day-of-month/day-of-week ambiguity;
  • account for the local time zone and daylight-saving transitions when choosing a run time.

What cron Is and Why It Exists

cron is a daemon — a process that starts at boot and keeps running in the background with no controlling terminal. Once a minute, it wakes up, reads a set of schedule files called crontabs, and compares the current minute against every line in them. Any line whose time fields match the current minute has its command executed immediately, in a new shell.

systemctl status cron
● cron.service - Regular background program processing daemon
     Loaded: loaded (/lib/systemd/system/cron.service; enabled)
     Active: active (running) since Tue 2026-07-28 06:12:04 UTC; 1 day ago

Loaded: shows the unit file was found and is enabled (it starts at boot); the line that actually matters is Active: active (running), which confirms the daemon is up right now. If cron has crashed or was never started this reads Active: inactive (dead) or failed, and in that state no scheduled job on the host runs at all, silently, until it is restarted with sudo systemctl start cron. Checking this one line is the first move when every job on a machine has stopped firing, as opposed to just one misbehaving line.

Naming across distributions

On Debian and Ubuntu the package and service are called cron. On the RHEL family (RHEL, CentOS, Rocky, Alma) the same role is filled by crond, so the equivalent command there is systemctl status crond. Both run the same crontab format; only the service name and some packaging details differ — covered in more depth in crontab and System cron.

Because cron is a long-running daemon rather than something invoked by hand, a job scheduled with it keeps running even when no one is logged in, including after an SSH session that closed hours ago. That property is what makes it suitable for unattended maintenance: log rotation, database dumps, certificate-renewal checks, and health-check scripts all depend on it.

The Crontab Line: Five Fields, Then a Command

Every non-comment, non-blank line in a crontab has the same shape:

minute hour day-of-month month day-of-week command
Field Allowed values Meaning
minute 0–59 Minute of the hour
hour 0–23 Hour of the day, 24-hour clock
day-of-month 1–31 Day of the calendar month
month 1–12 Month number
day-of-week 0–7 Day of the week; both 0 and 7 mean Sunday, 1 is Monday

A job runs only in the minute when all five fields match the current date and time. There is no field for the year, so a line that matches today will keep matching every year until it is removed.

0 7 * * 1 /opt/sales/bin/weekly-report

Reading this left to right: minute 0, hour 7, any day of the month, any month, weekday 1 (Monday). The job runs at 07:00 every Monday. The two asterisks in the day-of-month and month fields mean "no restriction on this field" — the job is tied to the weekday, not to a particular calendar date.

Wildcards, Lists, Ranges, and Steps

A bare * matches every possible value for a field. Beyond that, cron accepts three more constructs in any field:

  • List — comma-separated values: 0,30 in the minute field means "at minute 0 and minute 30."
  • Range — a dash between two values: 0-4 in the minute field means "at minutes 0, 1, 2, 3, and 4."
  • Step — a slash after a range or *: */15 in the minute field means "every 15 minutes starting from 0," i.e. 0, 15, 30, 45.
*/2 * * * * /usr/local/bin/heartbeat.sh
0-4 * * * * /usr/local/bin/burst-check.sh
15,45 * * * * /usr/local/bin/twice-hourly.sh

The first line runs every 2 minutes, around the clock. The second runs once a minute for the first five minutes of every hour — five separate executions, not one job spanning five minutes, since cron only ever starts a job and never tracks how long it runs. The third runs twice per hour, at the 15-minute and 45-minute marks.

Interview angle: cron's granularity

cron's finest resolution is one minute — there is no way to schedule something every 10 seconds with cron alone. If asked how to run a health check every 10 seconds, the honest answer is that cron is the wrong tool; a small while loop inside a systemd service, or a systemd timer with OnUnitActiveSec=10s, is the better fit (see systemd Timers: An Alternative to cron).

The day-of-month / day-of-week Trap

Two fields describe "which day," and when both are restricted at the same time, the common cron implementations — including Debian's cron and the Vixie-derived crond on RHEL — combine them with OR, not AND:

0 9 1 * 1 /usr/local/bin/monthly-and-monday-report.sh

A newcomer reads this as "the 1st of the month, if it also happens to be a Monday" — but cron runs it whenever the day-of-month is 1 or the day-of-week is Monday, whichever comes first. This single behavior causes more unexpected extra runs than any other part of crontab syntax. If a job genuinely needs "the 1st, only when it's a Monday," the reliable fix is to leave one of the two fields as * and check the extra condition inside the script itself (with date +%u, for instance), rather than expecting the crontab syntax to express an AND.

Shortcut Strings

Vixie-cron-style implementations, including the ones shipped by Debian, Ubuntu, and RHEL, accept a handful of @-prefixed shortcuts in place of the five time fields:

Shortcut Equivalent to Meaning
@reboot Run once, at daemon startup
@yearly / @annually 0 0 1 1 * Once a year, midnight of January 1
@monthly 0 0 1 * * Once a month, midnight of the 1st
@weekly 0 0 * * 0 Once a week, midnight on Sunday
@daily / @midnight 0 0 * * * Once a day, at midnight
@hourly 0 * * * * Once an hour, at minute 0
@reboot /usr/local/bin/mount-data-volume.sh
@daily /usr/local/bin/rotate-app-logs.sh

@reboot is the one shortcut with no five-field equivalent: it fires once when the cron daemon itself starts, which in practice means once per system boot. It is commonly used for tasks that must run exactly once after startup — mounting an extra volume, warming a cache, or starting a helper process that systemd does not manage directly.

Shortcuts are a convention, not a guarantee

@reboot and the other shortcuts are supported by the common cron implementations on Debian, Ubuntu, and RHEL, but the man page notes that support can vary by implementation. Run man 5 crontab on the target system before relying on a shortcut in a script meant to be portable across distributions.

Time Zones and Daylight Saving

cron evaluates every schedule against the system's local time zone, not against UTC (unless the machine's clock is itself set to UTC). Which zone a host uses is worth confirming before trusting a schedule:

timedatectl | grep 'Time zone'
                Time zone: Europe/London (BST, +0100)

Two consequences of this catch people out in interviews and in production:

  • Changing a host's zone silently shifts every job. A 0 3 * * * line means "3 a.m. local time"; move that server to another region, or change its timedatectl set-timezone, and the same line now fires at 3 a.m. in the new zone. On a fleet spanning regions the safest convention is to keep servers on UTC, so one schedule means the same wall-clock instant everywhere.
  • Daylight-saving transitions distort the early-morning window. When the clock springs forward (e.g. 01:59 → 03:00) an hour of wall-clock time never occurs; when it falls back, an hour occurs twice. Exactly how a given cron implementation treats a fixed-hour job scheduled inside that window — skip it, run it once, or run it twice — varies, so the portable rule is simply to avoid scheduling fixed-time jobs between 01:00 and 03:00. Jobs written with a step (*/30) rather than a fixed hour aren't tied to a specific vanished minute and are unaffected.

Practical Scenario: Choosing the Right Schedule

Problem. An application writes verbose debug logs to /var/log/myapp/, and the partition fills up roughly once a week if nothing cleans it out. A job is needed that removes logs older than 7 days, running at a quiet hour.

Check current disk usage:

du -sh /var/log/myapp/
2.3G    /var/log/myapp/

Design the schedule. Running once a day, at an hour when traffic is lowest (03:00), on every day of the month, is enough for a job that only needs to catch up on a 7-day retention window:

0 3 * * * find /var/log/myapp/ -name '*.log' -mtime +7 -delete

Result. Verify after the next scheduled run:

find /var/log/myapp/ -name '*.log' -mtime +7

An empty result confirms nothing older than 7 days remains. How to actually register this line — crontab -e versus a system-wide file — is covered next, in crontab and System cron.

Conclusion. The time fields describe when; the daemon guarantees that it runs, unattended. Getting the schedule right up front, and being explicit about day-of-month versus day-of-week, avoids surprise executions later.

Common Mistakes

  • Assuming day-of-month and day-of-week combine with AND. They combine with OR whenever both are restricted; see the dedicated section above.
  • Writing */1 instead of *. Both mean "every minute" in the minute field, but */1 is a needlessly confusing way to write it; use a plain * unless the step is greater than 1.
  • Forgetting there is no year field. A one-off job scheduled with a five-field line keeps running every year unless it is manually removed afterward.
  • Trusting a schedule without testing it. It is easy to misread a range or a comma. Cross-check the next few run times by hand, or temporarily set the job to * * * * * in a scratch file to confirm the command itself works before fixing the real schedule.

Exercises

  1. Write a crontab line that runs /usr/local/bin/sync.sh every 10 minutes, around the clock. Verify your answer by listing the six minute values within a single hour it would trigger on.
  2. A teammate wants a job to run "at half past 2 AM, but only on weekdays." Write the line, and state which field encodes "weekdays" and what values it uses.
  3. Explain, in your own words, why 30 6 15 * 3 /usr/local/bin/job.sh will very likely run more often than someone unfamiliar with the day-of-month/day-of-week rule would expect. Describe the fix without changing either day field's individual values.

Summary

cron is a daemon that starts commands on a schedule, starts automatically at boot, and follows a five-field crontab format. This article covered only the time syntax; actually installing a schedule, and the difference between a user's own crontab and the system-level crontab, continues in crontab and System cron.

References