Cron and Scheduled Tasks¶
Automating repetitive tasks is one of the most practical things you can do as a system administrator. Instead of remembering to run backups at midnight, rotate logs every Sunday, or check disk space every hour, you schedule the job once and let the system handle it. Linux provides two main tools for this: cron (the traditional scheduler) and systemd timers (the modern alternative).
How Cron Works¶
The cron daemon (crond or cron, depending on your distribution) runs continuously in the background. Every minute, it wakes up, checks all configured crontab files, and runs any commands whose schedule matches the current time.
Each cron entry follows a fixed format - 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, where 0 and 7 are Sunday)
│ │ │ │ │
* * * * * command to execute
Each field accepts specific values, ranges, steps, and lists:
| Syntax | Meaning | Example |
|---|---|---|
* |
Every possible value | * * * * * runs every minute |
5 |
Exact value | 5 * * * * runs at minute 5 of every hour |
1,15 |
List of values | 0 1,15 * * * runs at 1:00 AM and 3:00 PM |
1-5 |
Range | 0 9 * * 1-5 runs at 9:00 AM Monday through Friday |
*/10 |
Step (every Nth) | */10 * * * * runs every 10 minutes |
Month and day-of-week fields also accept three-letter abbreviations: jan, feb, mon, tue, etc.
Managing User Crontabs¶
Every user on the system can have their own crontab. The crontab command manages it:
crontab -e # Edit your crontab (opens in $EDITOR)
crontab -l # List your current crontab entries
crontab -r # Remove your entire crontab (use with caution)
When you run crontab -e, your editor opens with the current crontab contents (or an empty file if you don't have one yet). Add entries, save, and exit. Cron validates the syntax and installs the new crontab immediately.
User crontabs are stored in /var/spool/cron/crontabs/ (Debian/Ubuntu) or /var/spool/cron/ (RHEL/CentOS). You should never edit these files directly - always use crontab -e.
crontab -r deletes everything
crontab -r removes your entire crontab with no confirmation. If you want to remove a single entry, use crontab -e and delete the specific line. Some administrators alias crontab -r to crontab -ri for interactive confirmation, but this alias doesn't exist by default.
System Crontab Files¶
Beyond per-user crontabs, the system has several locations for scheduled tasks managed by root:
/etc/crontab is the system-wide crontab. It has the same five time fields as a user crontab, but adds a sixth field - the username to run the command as:
# /etc/crontab - system-wide cron entries
SHELL=/bin/bash
PATH=/sbin:/bin:/usr/sbin:/usr/bin
# minute hour dom month dow user command
*/15 * * * * root /usr/local/bin/check-disk-space.sh
0 3 * * * root /usr/local/bin/backup.sh
/etc/cron.d/ holds individual crontab fragment files. Each file uses the same six-field format as /etc/crontab. Packages often install their cron jobs here - for example, a monitoring agent might drop a file at /etc/cron.d/monitoring instead of modifying /etc/crontab. This keeps system cron jobs modular and easy to manage.
Drop-in directories provide a simpler approach for common schedules. Any executable script placed in these directories runs on the corresponding schedule:
| Directory | Schedule |
|---|---|
/etc/cron.hourly/ |
Once per hour |
/etc/cron.daily/ |
Once per day |
/etc/cron.weekly/ |
Once per week |
/etc/cron.monthly/ |
Once per month |
Scripts in these directories don't need cron syntax at all - they just need to be executable (chmod +x). The exact run time depends on your system's configuration in /etc/crontab or /etc/anacrontab.
Cron Environment and Gotchas¶
Cron jobs run in a minimal environment that is very different from your interactive shell. This is the most common source of cron job failures.
PATH is minimal: Cron's default PATH is typically /usr/bin:/bin. Commands that work fine in your terminal may fail in cron because they live in /usr/local/bin, /sbin, or elsewhere. Always use full paths to commands in cron jobs, or set PATH explicitly at the top of your crontab:
# Set PATH in your crontab
PATH=/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
# Or use full paths in each command
0 3 * * * /usr/local/bin/restic backup /home
No shell profile: Cron doesn't source ~/.bashrc, ~/.bash_profile, or any other profile files. Environment variables, aliases, and functions from your shell configuration don't exist in cron. If your script depends on environment variables, set them explicitly in the crontab or source them in the script itself.
Output goes to email: By default, cron emails any output (stdout and stderr) from a job to the crontab owner. If no mail system is configured, output is silently discarded - you'll never know your job failed. Handle output explicitly:
# Redirect all output to a log file
0 3 * * * /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1
# Discard output entirely (only if you truly don't care)
*/5 * * * * /usr/local/bin/health-check.sh > /dev/null 2>&1
# Send to a specific email
MAILTO=admin@example.com
0 6 * * 1 /usr/local/bin/weekly-report.sh
Debugging cron jobs
When a cron job doesn't work, test it the way cron runs it: env -i /bin/sh -c 'your-command-here'. This starts with an empty environment, just like cron does. If the command fails here but works in your terminal, the problem is almost certainly an environment issue (missing PATH, environment variables, or shell features).
Overlapping jobs: If a job runs longer than the interval between executions, you'll get multiple instances running simultaneously. Use flock to prevent overlap:
# Only one instance runs at a time - others skip if locked
*/5 * * * * flock -n /tmp/backup.lock /usr/local/bin/backup.sh
The -n flag makes flock exit immediately (rather than wait) if the lock is already held, so the second instance silently skips instead of queuing up.
Anacron¶
Standard cron assumes the machine is always running. If a daily job is scheduled for 3:00 AM and the machine is off at that time, the job simply doesn't run - cron doesn't retry it later. Anacron solves this for periodic jobs on machines that aren't always on, like laptops and desktops.
Anacron doesn't run at precise times. Instead, it checks whether a job has run within its required period (daily, weekly, monthly). If not, it runs the job with a configurable delay after boot. Configuration lives in /etc/anacrontab:
# /etc/anacrontab
# period delay identifier command
1 5 cron.daily run-parts /etc/cron.daily
7 10 cron.weekly run-parts /etc/cron.weekly
@monthly 15 cron.monthly run-parts /etc/cron.monthly
The fields are:
| Field | Meaning |
|---|---|
| Period | How often the job should run (in days, or @monthly) |
| Delay | Minutes to wait after anacron starts before running the job |
| Identifier | A unique name used for the timestamp file in /var/spool/anacron/ |
| Command | The command to execute |
Anacron records when each job last ran in /var/spool/anacron/. On most modern distributions, anacron is triggered by cron itself (via a daily cron job) or by a systemd timer. The delay values prevent all jobs from running at once and overwhelming the system during boot.
On most desktop distributions, the /etc/cron.daily/, /etc/cron.weekly/, and /etc/cron.monthly/ directories are already managed by anacron. This means scripts you place there will run reliably even if the machine was off during the scheduled time.
Systemd Timers¶
On systems that use systemd (most modern Linux distributions), timer units provide an alternative to cron. Timers are paired with service units - the timer defines the schedule, and the service defines the command to run.
To see all active timers on a system:
This shows each timer's next and last trigger time, the associated service, and whether it's active.
Creating a Timer¶
A systemd timer requires two unit files: a .service file (what to run) and a .timer file (when to run it). Here's an example that runs a backup script daily at 3:00 AM:
/etc/systemd/system/backup.service:
/etc/systemd/system/backup.timer:
[Unit]
Description=Run backup daily at 3:00 AM
[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=true
[Install]
WantedBy=timers.target
Enable and start the timer:
The Persistent=true setting is the systemd equivalent of anacron - if the system was off when the timer should have fired, it runs the job as soon as possible after boot.
Timer Schedule Syntax¶
Systemd uses a calendar expression format that's more readable than cron's five fields:
| Expression | Meaning |
|---|---|
*-*-* 03:00:00 |
Every day at 3:00 AM |
Mon *-*-* 09:00:00 |
Every Monday at 9:00 AM |
*-*-01 00:00:00 |
First day of every month at midnight |
*-*-* *:00/15:00 |
Every 15 minutes |
You can also use relative timers with OnBootSec (time after boot) and OnUnitActiveSec (time after last run):
This runs 5 minutes after boot and then every hour after each successful run.
Use systemd-analyze calendar to test your expressions:
Cron vs Systemd Timers¶
Both tools schedule tasks, but they have different strengths:
| Feature | Cron | Systemd Timers |
|---|---|---|
| Configuration | Single line per job | Two unit files per job |
| Logging | Email or manual redirect | Automatic journald integration |
| Missed runs | Lost (unless anacron) | Persistent=true handles it |
| Dependencies | None | Can depend on other services |
| Resource control | None | Full cgroup integration (CPU, memory limits) |
| Randomized delay | Not built-in | RandomizedDelaySec prevents thundering herd |
| Learning curve | Simple | More verbose, more powerful |
Use cron for simple, quick-to-set-up scheduled commands - especially on systems where you need to add or modify jobs frequently. The single-line format is hard to beat for convenience.
Use systemd timers when you need logging integration, dependency management, resource limits, or reliability for missed runs. If your system already uses systemd for everything else, timers keep your configuration consistent.
Practical Exercise¶
Further Reading¶
- crontab(5) man page - crontab file format reference
- crontab.guru - interactive cron expression editor and explainer
- systemd.timer(5) - systemd timer unit documentation
- systemd.time(7) - calendar event and time span syntax reference
- Anacron man page - anacron configuration and behavior
- flock(1) - file-based locking for preventing concurrent cron job execution
Previous: Best Practices | Back to Index