If you've spent any time working with Linux servers, CI/CD pipelines, or web applications, you've probably encountered the word "cron." Maybe you've seen a crontab file on a server, a GitHub Actions workflow with on: schedule:, or a CronJob in a Kubernetes manifest. They all trace back to the same concept: a scheduled task that runs automatically at defined time intervals, without anyone pressing a button.
This guide explains what cron is, how it works, how to read cron syntax, and where you'll encounter cron in real-world systems.
A cron job is a scheduled task that runs automatically on Unix and Linux systems at a specified time or interval. The name comes from Chronos (χρόνος), the Greek word for time.
Cron jobs are used for any work that needs to happen on a schedule rather than on demand:
The key property: cron jobs run in the background, unattended, without any user interaction. Once scheduled, they run reliably — until they don't (more on that later).
On a Linux server, cron is managed by a background process called crond (the cron daemon). It runs continuously, waking up every minute to check whether any scheduled jobs need to run. When the current time matches a job's schedule, crond executes the associated command.
Jobs are defined in crontab files (cron table). Each user on a system can have their own crontab, and there's a system-wide crontab at /etc/crontab.
# List your current cron jobs
crontab -l
# Open your crontab in an editor
crontab -e
# Remove your entire crontab (careful!)
crontab -r
When you run crontab -e, you'll see a file where each line defines one scheduled job. Lines starting with # are comments. Every other line is a schedule definition followed by the command to run.
Every cron job is defined by five time fields followed by the command to execute:
┌───────────── minute (0–59)
│ ┌─────────── hour (0–23)
│ │ ┌───────── day of month (1–31)
│ │ │ ┌─────── month (1–12)
│ │ │ │ ┌───── day of week (0–7, both 0 and 7 = Sunday)
│ │ │ │ │
* * * * * command-to-run
| Position | Field | Allowed values | Special values |
|---|---|---|---|
| 1st | Minute | 0–59 | */15 = every 15 minutes |
| 2nd | Hour | 0–23 | 0,12 = midnight and noon |
| 3rd | Day of month | 1–31 | 1,15 = 1st and 15th |
| 4th | Month | 1–12 | */3 = every quarter |
| 5th | Day of week | 0–7 (0 & 7 = Sun) | 1-5 = Mon–Fri |
* — matches any value ("every")*/n — every n units (*/5 in minutes = every 5 minutes)a-b — a range (1-5 in weekday = Monday through Friday)a,b,c — a list (1,15 in day = the 1st and 15th)| Expression | Meaning |
|---|---|
| * * * * * | Every minute |
| 0 * * * * | Every hour, on the hour |
| 0 2 * * * | Every day at 2:00am |
| 0 9 * * 1-5 | Weekdays (Mon–Fri) at 9:00am |
| 0 0 1 * * | First day of every month at midnight |
| */15 * * * * | Every 15 minutes |
| 30 3 * * 0 | Every Sunday at 3:30am |
| 0 0 1 1 * | January 1st at midnight (yearly) |
| 0 9 * * 1 | Every Monday at 9:00am |
| */30 9-17 * * 1-5 | Every 30 min during business hours, weekdays |
Most cron implementations also accept human-readable shorthand:
| Shorthand | Equivalent | Meaning |
|---|---|---|
@yearly | 0 0 1 1 * | Once a year, Jan 1st midnight |
@monthly | 0 0 1 * * | Once a month, 1st at midnight |
@weekly | 0 0 * * 0 | Once a week, Sunday midnight |
@daily | 0 0 * * * | Once a day at midnight |
@hourly | 0 * * * * | Once an hour |
@reboot | — | Once at system startup |
# Back up the database every day at 2am
0 2 * * * /opt/scripts/backup.sh
# Send a weekly report every Monday at 9am
0 9 * * 1 /opt/scripts/weekly-report.py
# Clean up temp files every hour
0 * * * * find /tmp -name "*.tmp" -mtime +1 -delete
# Rotate logs every night at midnight
0 0 * * * /usr/sbin/logrotate /etc/logrotate.conf
Linux crontab is the original, but the same concept appears across almost every modern deployment platform. Once you understand cron syntax, you can use it everywhere.
on: schedule:)on:
schedule:
- cron: '0 3 * * *' # Run every day at 3am UTC
jobs:
nightly-build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm test
GitHub Actions scheduled workflows use standard cron syntax. One important caveat: GitHub silently disables scheduled workflows on repositories with no recent push activity (approximately 60 days). The workflow just stops running — no error, no notification.
# AWS cron expression — note: year field added, no seconds
cron(0 3 * * ? *) # Every day at 3am UTC
cron(0 9 ? * MON *) # Every Monday at 9am UTC
AWS uses a 6-field cron syntax that adds a year field and uses ? for "no specific value" in the day-of-month and day-of-week fields.
apiVersion: batch/v1
kind: CronJob
metadata:
name: nightly-backup
spec:
schedule: "0 2 * * *"
jobTemplate:
spec:
template:
spec:
containers:
- name: backup
image: my-backup-image
Kubernetes uses standard 5-field cron syntax. CronJobs create a new Pod on schedule. Failed pods are visible in the cluster, but missed runs (e.g., the cluster was down during the scheduled window) are not automatically retried.
Heroku Scheduler uses a simplified UI (every 10 min / every hour / every day at X time) rather than raw cron syntax, but it's conceptually the same: a command that runs on a schedule in your application's environment.
Virtually every platform that runs application code has a scheduling primitive: Railway, Fly.io, Render, Vercel (via cron triggers), Laravel's task scheduler, Celery Beat, APScheduler, and the schedule Python library. All of them implement the cron concept, whether or not they use the exact cron syntax.
Cron is good at one thing: running a command at a specified time. It doesn't do much else.
Specifically, cron does not:
This is why cron monitoring exists. The standard pattern is a dead man's switch: your job sends an HTTP ping at the end of each successful run. A monitoring service watches for that ping on the expected schedule. If the ping doesn't arrive — because the job failed, because the scheduler crashed, because the server went down — you get an alert.
Adding monitoring to a crontab entry is one extra line:
# Backup with monitoring — ping only fires if backup succeeds
0 2 * * * /opt/scripts/backup.sh && curl -fsS https://cronwatch.io/ping/YOUR-TOKEN
The && ensures the curl only runs if the script exits with code 0. If the script fails, no ping — and the monitoring service alerts you.
CronWatch monitors your scheduled jobs with a dead man's switch. Add one line to your cron job. If the ping stops arriving — because the job failed, stalled, or the scheduler itself crashed — you get an email alert. Works with crontab, GitHub Actions, AWS EventBridge, Kubernetes CronJobs, and Heroku Scheduler.
Join the CronWatch waitlist → Test your cron expression free| Concept | What it is |
|---|---|
| cron | The Unix scheduling system; runs jobs on a time-based schedule |
| crond | The background daemon that reads crontabs and runs scheduled commands |
| crontab | The file that defines scheduled jobs; one job per line |
| cron expression | The 5-field time specification (* * * * *) that defines when a job runs |
| cron job | A scheduled command defined in a crontab |
| dead man's switch | A monitoring pattern where the job signals success; alert fires if signal is absent |