What Is a Cron Job? The Complete Guide

Cron has scheduled work on Unix systems for fifty years. The syntax takes ten minutes to learn, and the ways it quietly fails take rather longer.

Updated 5 August 2026 · 10 min read

A cron job is a command that a Unix-like system runs automatically on a fixed schedule. The schedule is written as five fields (minute, hour, day of month, month, day of week) followed by the command, so 0 3 * * * means 3 a.m. every day. Jobs are listed in a file called a crontab, edited with crontab -e.

Key takeaways

  • Five fields, in order: minute, hour, day of month, month, day of week. Then the command.
  • An asterisk means every value. */5 means every fifth value, so */5 * * * * is every five minutes.
  • One minute is the floor. Cron cannot schedule anything more frequent than that.
  • Cron does not read your shell profile, so a job that works in your terminal can still fail. Use absolute paths.
  • Cron reports nothing when a job fails, hangs, or never starts. Silence looks identical to success.

What a cron job actually is

Three words get used interchangeably and mean different things. Cron is the background service that has been running on Unix systems since the 1970s. A crontab (cron table) is the file that lists what to run and when. A cron job is one line in that file: a single command on a single schedule.

The mechanism is deliberately simple. The cron daemon wakes once a minute, compares the current time against every scheduled entry it knows about, and runs whatever matches. It does not queue, retry, or check the result. It starts things on time, and that is the whole of its job description.

Typical uses are the unglamorous work that keeps a server healthy: nightly database backups, log rotation, clearing temporary files, sending scheduled reports, warming caches, renewing TLS certificates, and pulling data from an API on a fixed interval.

The five fields

Every crontab line starts with five time fields, then the command. Read left to right:

PositionFieldAllowed values
1Minute0 to 59
2Hour0 to 23 (24-hour clock)
3Day of month1 to 31
4Month1 to 12, or JAN to DEC
5Day of week0 to 7 (0 and 7 both mean Sunday), or SUN to SAT

Four operators cover almost everything you will write. An asterisk * means every value. A comma lists values, as in 1,15,30. A hyphen gives a range, as in 9-17. A slash sets a step, so */5 means every fifth value and 0-30/10 means minutes 0, 10, 20 and 30.

Step values (*/5) and name abbreviations (MON) are extensions from Vixie cron rather than the original POSIX specification. Every mainstream Linux distribution supports them. Some minimal systems, notably BusyBox on embedded devices, do not.

Cron schedule examples

The fastest way to learn the syntax is to read it. Each of these is a complete schedule, ready to paste in front of a command:

ExpressionWhen it runs
* * * * *Every minute
*/5 * * * *Every 5 minutes
0 * * * *Every hour, on the hour
30 2 * * *Every day at 02:30
0 9 * * 1-5At 09:00, Monday to Friday
0 0 1 * *Midnight on the first of every month
0 0 * * 0Midnight every Sunday
*/15 9-17 * * 1-5Every 15 minutes, 9 a.m. to 5 p.m., weekdays
0 4 1 1 *Once a year, 1 January at 04:00

If you would rather not count fields by hand, the free cron expression generator builds a schedule from dropdowns, translates any existing expression into plain English, and previews the next several run times.

The shorthand strings

Cron accepts a handful of named schedules in place of all five fields, which are easier to read at a glance:

  • @hourly is the same as 0 * * * *
  • @daily or @midnight is the same as 0 0 * * *
  • @weekly is the same as 0 0 * * 0
  • @monthly is the same as 0 0 1 * *
  • @yearly or @annually is the same as 0 0 1 1 *
  • @reboot runs once when the cron daemon starts, which usually means at boot

How to create a cron job

Your personal crontab is edited through the crontab command rather than by opening a file directly, because that lets cron validate the syntax and reload it for you:

  1. Run crontab -e. The first time, it may ask which editor to use.
  2. Add a line with the five time fields, then the command, using absolute paths: 0 3 * * * /usr/bin/php /var/www/app/backup.php
  3. Save and exit. Cron picks the change up immediately, with no restart needed.
  4. Confirm it is registered with crontab -l, which lists your jobs.

Avoid crontab -r. It deletes your entire crontab immediately, with no confirmation prompt, and it sits one key away from -e on the keyboard.

Where crontabs live

Jobs can be defined in several places, which is why a job you cannot find may still be running:

  • User crontabs, managed with crontab -e and stored under /var/spool/cron/. These run as that user and have no user column.
  • /etc/crontab, the system-wide file. It has a sixth column naming the user to run as.
  • /etc/cron.d/, where packages drop their own job files. Same format as /etc/crontab.
  • /etc/cron.hourly, .daily, .weekly, .monthly, directories of scripts run on those intervals, usually by run-parts or anacron.

Build a cron schedule without counting fields

The free cron expression generator writes the schedule for you, decodes any expression into plain English, and previews the next run times.

Why cron jobs fail

A job that runs perfectly when you type it into a terminal and does nothing at all from cron is the single most common cron problem, and it nearly always comes down to one of the following. If you are debugging one right now, the cron job not running checklist works through these in diagnostic order.

The environment is not your shell

Cron runs jobs in a deliberately minimal environment. It does not read .bashrc, .bash_profile or .profile, so none of your aliases, exported variables, or PATH additions exist. PATH is typically just /usr/bin:/bin, which is why a job calling python, node, php or anything installed through a version manager fails with "command not found".

The fix is to be explicit. Use the absolute path to the interpreter and the script (/usr/bin/python3 /opt/app/task.py, not python3 task.py), and set any variables the job needs at the top of the crontab itself.

The working directory is not where you think

Cron starts every job in the owner's home directory, not in the directory the script lives in. Any relative path inside the script resolves from there. Either use absolute paths throughout, or begin the command with a cd: cd /var/www/app && ./task.sh.

The percent sign is not a percent sign

Inside a crontab, % is a special character. The first one ends the command and everything after it is fed to the job as standard input, with each subsequent % becoming a newline. This quietly breaks the most natural thing in the world, dating a filename: date +%Y-%m-%d truncates to date +. Escape every percent sign as \%.

Three fixes solve most cron failures: absolute paths for everything, escape % as \%, and redirect output to a log file so there is something to read when it goes wrong.

Missing final newline

A crontab file must end with a newline. A missing one produces the memorable error "errors in crontab file, can't install", and the last job in the file silently never runs. Editing through crontab -e normally handles this for you; pasting a file into place does not.

Where cron output goes

By default, cron emails anything a job writes to standard output or standard error to the user who owns the crontab. On a modern server that mail almost never arrives, because no mail transfer agent is installed. The log records the attempt with a line reading "(CRON) info (No MTA installed, discarding output)", and your job output is gone.

Redirect it somewhere you can read instead. Appending >> /var/log/mytask.log 2>&1 to the command captures both normal output and errors. To confirm cron at least tried to start a job, check the system log:

SystemWhere cron logs go
Debian, Ubuntu/var/log/syslog, or journalctl -u cron
RHEL, CentOS, Fedora/var/log/cron, or journalctl -u crond
macOSlog show --predicate 'process == "cron"'

Read that log carefully, because it answers a specific question and only that one. A line there means cron started the command. It says nothing about whether the command succeeded. Where cron logs are stored covers reading them per distribution, and how to capture job output properly.

The gotcha nobody expects: day of month and day of week

This one is in the manual and still catches experienced people. When both the day-of-month and day-of-week fields are restricted, meaning neither is an asterisk, cron runs the job when either matches, not both.

So 0 0 13 * 5 does not mean "Friday the 13th". It means midnight on the 13th of every month, and midnight every Friday. If you genuinely need both conditions, put the day-of-week test inside the command: 0 0 13 * * [ "$(date +\%u)" = "5" ] && /path/to/script.sh.

Restrict day-of-month and day-of-week together and cron ORs them, it does not AND them. This is the documented behaviour, not a bug, and it is the reason some jobs run far more often than intended.

Cron tells you nothing when a job stops

Everything above concerns jobs that fail loudly enough to investigate. The more expensive problem is the one that makes no noise at all.

Cron has no concept of success. It does not check exit codes, it will not retry, it does not alert, and it has no view of whether a job that started ever finished. A backup script that has been exiting with an error every night for three weeks produces exactly the same silence as one that has worked perfectly. So does a script that hangs on a network call and never returns, a server where the cron daemon is not running, and a crontab that was wiped during a migration.

The consequence is a specific and familiar kind of incident: nobody discovers the backups stopped until somebody needs a backup. The gap is not that cron is unreliable, it is that cron was never designed to report.

Closing the gap with heartbeat monitoring

The standard fix inverts the usual monitoring model. Instead of an external service checking whether your server responds, the job itself checks in when it finishes, and the absence of that check-in is what raises the alarm. This is heartbeat monitoring, sometimes called dead man's switch monitoring.

In practice it is one line appended to the command. The job runs, and on success it pings a unique URL. If the ping does not arrive within the expected window, you get alerted. Because the alert is triggered by silence, it catches every failure mode that ordinary logging misses: the job that errored, the job that hung, the daemon that stopped, and the crontab that no longer exists.

Pulsetic does this as cron job monitoring, alongside the expression generator for writing the schedule in the first place.

Cron reports nothing. If a scheduled job matters, monitor its absence, not just its output: a job that never starts writes no error log to find.

Alternatives to cron

Cron is not the only scheduler, and on some platforms it is no longer the default one. The five-field syntax, however, has outlived the program that introduced it and turns up almost everywhere.

OptionWhere it fitsUses cron syntax
systemd timersModern Linux. Better logging, dependency handling and randomised delays.No, its own format
Kubernetes CronJobScheduled work in a cluster, running as pods.Yes
AWS EventBridge SchedulerServerless scheduling on AWS.Yes, with an extra year field
Windows Task SchedulerThe Windows equivalent of cron.No, GUI and XML
anacronLaptops and desktops that are not always on. Runs missed jobs after boot.No, interval in days
Application schedulersLaravel, Celery, Quartz, Sidekiq, node-cron and similar.Usually yes

One thing none of them change is the reporting problem. A Kubernetes CronJob that fails silently is exactly as invisible as a crontab entry that does, so whichever scheduler you choose, something still has to notice when the work stops happening.

See how Pulsetic's cron job monitoring catches this from the outside, across 15+ locations.

Frequently asked questions

  • What does * * * * * mean in cron?

    Every minute of every hour of every day. The five asterisks stand for minute, hour, day of month, month and day of week, and an asterisk means every value for that field.

  • Can a cron job run every 30 seconds?

    Not directly. Cron evaluates its schedule once a minute, so one minute is the shortest interval it can express. The usual workarounds are a loop with a sleep inside the script, two jobs offset by 30 seconds, or a systemd timer, which supports sub-minute intervals.

  • Why does my cron job work manually but not in cron?

    Almost always the environment. Cron does not read your shell profile, so your PATH and exported variables are missing and commands cannot be found. Use absolute paths for the interpreter and the script, and set any required variables at the top of the crontab.

  • How do I check whether a cron job ran?

    Look in the system log: /var/log/syslog on Debian and Ubuntu, /var/log/cron on RHEL and Fedora. That confirms cron started the command but not that it succeeded. For that, redirect the job output to a log file, and use heartbeat monitoring to be alerted when an expected run does not happen at all.