Cron jobs: scheduling recurring tasks on a Linux server
Quick answer
Cron runs commands on a schedule you define with five time fields plus the command itself. Edit your own schedule with crontab -e, or drop a file into /etc/cron.d/ for system-wide jobs. The most common failure mode isn't a bad schedule, it's a script that only works when you run it by hand, because cron gives it almost none of your normal shell environment.
The five fields
A crontab line has five time fields, in this order, followed by the command to run:
| 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 month |
| Month | 1-12 | Month of the year |
| Day of week | 0-7 (0 and 7 both mean Sunday) | Day of the week |
An asterisk (*) means "every value" for that field. A slash sets a step, so */15 in the minute field means every 15 minutes. A comma lists specific values, and a hyphen sets a range.
Real examples
These are complete, working crontab lines. The path to the script is deliberately absolute, more on why below.
# Run a backup script every day at 3:00am
0 3 * * * /opt/scripts/backup.sh
# Run a health check every 15 minutes
*/15 * * * * /opt/scripts/check.sh
# Run a report on the first day of every month at 6:00am
0 6 1 * * /opt/scripts/monthly-report.shEditing your crontab
Per-user crontab
Run crontab -e as the user whose schedule you want to change. This opens the crontab in your default editor. Jobs added here run as that user, with that user's permissions.
Listing and removing
Run crontab -l to print the current user's crontab without opening an editor, and crontab -r to remove it entirely. Be careful with crontab -r: it deletes the whole crontab immediately, without asking for confirmation, so take a copy first with crontab -l > crontab.bak.
System-wide jobs
For jobs that shouldn't belong to a single user's crontab, place a file in /etc/cron.d/. The format is almost the same, but each line needs an extra field naming the user to run the command as:
0 3 * * * root /opt/scripts/backup.shThis is also the more discoverable option in configuration management, since the file lives under version control alongside the rest of your server setup rather than inside a per-user crontab that's easy to forget about.
Where cron jobs silently fail
A script that runs perfectly when you type it at your own terminal can still fail under cron. The reason is almost always environment, not logic.
- Minimal environment. Cron does not run a login shell, so the shell profile files that normally set up your
PATH, environment variables, and aliases never run. A script that callspython3ornodeand relies on your shell'sPATHto find them may simply not find them under cron. - Different working directory. Cron jobs typically start in the home directory of the user running them, not wherever you happened to be sitting when you tested the script manually. A script using a relative path like
./data.csvwill look in the wrong place. - No assumptions about environment variables. Anything your script depends on, such as an API key or a config path normally exported in your shell, needs to be set explicitly inside the script or the crontab entry, not assumed to already exist.
The fix for most of this is the same: use absolute paths everywhere, for the script itself, for any files it reads or writes, and for any binaries it calls. Don't assume a variable is set just because it always is when you run the script by hand.
Make failures visible
By default, cron only emails a job's output to the local mail account, which on most servers nobody is reading. A failed job can go unnoticed for weeks unless you build in a way to see it. The simplest habit is redirecting output to a log file:
0 3 * * * /opt/scripts/backup.sh >> /var/log/backup.log 2>&1The 2>&1 sends error output to the same place as standard output, so both successes and failures land in one file you can check, or feed into a monitoring check. Without this, cron failures are silent by default, the job simply doesn't run correctly and nothing tells you.