~/articles/cron-jobs-guide

Cron Jobs: The Complete Guide to Scheduling Tasks in Linux

Learn how to schedule automated tasks with cron โ€” syntax, examples, common pitfalls, and best practices for production crontabs.

9 min read

Cron is the time-based job scheduler in Unix-like systems. It has been a cornerstone of Linux administration since the 1970s, and it remains the go-to solution for running recurring tasks โ€” from log rotation and database backups to cache purges and monitoring checks. Despite its simplicity, cron has subtle behaviors that trip up even experienced engineers.

This guide covers everything from the five-field expression format to production-grade best practices that prevent the silent failures that make cron jobs notorious.

The Five-Field Cron Expression

A cron expression consists of five fields that define when a job runs. Each field represents a unit of time:

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ minute (0โ€“59)

โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ hour (0โ€“23)

โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ day of month (1โ€“31)

โ”‚ โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€ month (1โ€“12)

โ”‚ โ”‚ โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€ day of week (0โ€“7, 0 and 7 = Sunday)

โ”‚ โ”‚ โ”‚ โ”‚ โ”‚

* * * * * command-to-execute

Special characters include * (any value),, (list),- (range), and/ (step). For example,*/15 means "every 15 units."

Practical Examples

# Every day at 2:30 AM

30 2 * * * /opt/scripts/backup.sh

# Every 15 minutes

*/15 * * * * /usr/bin/health-check.sh

# Weekdays at 9 AM

0 9 * * 1-5 /opt/scripts/daily-report.sh

# First day of every month at midnight

0 0 1 * * /opt/scripts/monthly-cleanup.sh

# Every Sunday at 4 AM

0 4 * * 0 /opt/scripts/weekly-maintenance.sh

Managing Crontabs

Each user has their own crontab. The system also has crontab files in/etc/cron.d/ and time-based directories like/etc/cron.daily/.

$ crontab -e ย ย ย # edit your crontab

$ crontab -l ย ย ย # list your crontab

$ crontab -r ย ย ย # remove your crontab (careful!)

$ sudo crontab -u www-data -e # edit another user's crontab

For system-wide jobs, placing scripts in /etc/cron.d/ with a username field is often cleaner than editing root's crontab directly. These files survive package upgrades and are easier to manage with configuration management tools like Ansible.

The Environment Problem

The number one reason cron jobs fail silently: cron runs with a minimal environment. Your interactive shell has PATH, environment variables, and shell configurations loaded from.bashrc or.profile. Cron does not load any of these.

# This will probably fail in cron:

* * * * * node /opt/app/script.js

# This will work โ€” use absolute paths:

* * * * * /usr/bin/node /opt/app/script.js

# Or set PATH at the top of your crontab:

PATH=/usr/local/bin:/usr/bin:/bin

Always use absolute paths for both the command and any files it references. If your script depends on environment variables, define them at the top of the crontab or source a config file at the beginning of your script.

Output and Logging

By default, cron sends any output (stdout and stderr) to the user's local mailbox. On most modern servers, local mail is not configured, so this output is silently lost. Always redirect output explicitly:

# Log both stdout and stderr

30 2 * * * /opt/scripts/backup.sh >> /var/log/backup.log 2>&1

# Log with timestamps

30 2 * * * /opt/scripts/backup.sh 2>&1 | /usr/bin/ts '[%Y-%m-%d %H:%M:%S]' >> /var/log/backup.log

# Discard output (only if you truly don't care)

*/5 * * * * /opt/scripts/heartbeat.sh > /dev/null 2>&1

Never redirect to /dev/null unless you have separate monitoring in place. Silent failures in cron jobs can go unnoticed for weeks or months.

Preventing Overlapping Runs

If a cron job takes longer than its interval, you can end up with multiple copies running simultaneously. This is dangerous for jobs that modify databases, move files, or hold locks. Useflock to prevent overlap:

# Use flock to ensure only one instance runs

*/5 * * * * /usr/bin/flock -n /tmp/myapp.lock /opt/scripts/process-queue.sh

# -n means non-blocking: skip if lock is held

# -w 60 means wait up to 60 seconds for the lock

For critical jobs, combine flock with alerting: if the lock is held for longer than expected, your monitoring should flag it.

Cron vs. Systemd Timers

Modern Linux distributions offer systemd timers as an alternative to cron. Here is when each makes sense:

FeatureCronSystemd Timer
Setup complexitySimple (one line)Two files (.service + .timer)
LoggingManual redirectjournalctl built-in
DependenciesNoneAfter=network.target etc.
Overlap preventionManual (flock)Built-in
PortabilityAll Unix systemssystemd only

Use cron for simple, portable tasks. Use systemd timers when you need dependency ordering, built-in logging, resource controls (CPU/memory limits), or accurate sub-minute scheduling.

Production Best Practices

1. Always log output โ€” redirect both stdout and stderr to a file with timestamps.

2. Use absolute paths โ€” for commands, scripts, and files. Never rely on PATH.

3. Use flock for long-running jobs โ€” prevent dangerous overlapping executions.

4. Set MAILTO โ€” even if you redirect output, set MAILTO to catch unexpected errors.

5. Test with the cron environment โ€” run env -i /bin/sh -c 'your-command' to simulate cron's minimal environment.

6. Monitor job execution โ€” use a dead man's switch service or check logs to verify jobs ran.

7. Version control your crontabs โ€” store cron configs in git alongside your application code.

8. Stagger job times โ€” avoid scheduling everything at midnight to prevent resource spikes.

Related Tools