Cron jobs and task scheduling
In this series (15 parts)
- What is Linux and how it differs from other OSes
- Installing Linux and setting up your environment
- The Linux filesystem explained
- Users, groups, and permissions
- Essential command line tools
- Shell scripting fundamentals
- Processes and job control
- Standard I/O, pipes, and redirection
- The Linux networking stack
- Package management and software installation
- Disk management and filesystems
- Logs and system monitoring
- SSH and remote access
- Cron jobs and task scheduling
- Linux security basics for sysadmins
Cron is the traditional Unix task scheduler. It runs commands at specified times, whether that is every minute, every Monday at 3 AM, or once a year on January 1st. Systemd timers are the modern alternative with better logging and dependency management. You should know both.
Prerequisites
You should be comfortable with shell scripting and understand systemd basics.
Cron syntax
A cron job has five time fields followed by the command to run:
┌───────────── minute (0-59)
│ ┌───────────── hour (0-23)
│ │ ┌───────────── day of month (1-31)
│ │ │ ┌───────────── month (1-12)
│ │ │ │ ┌───────────── day of week (0-7, 0 and 7 = Sunday)
│ │ │ │ │
* * * * * command
Special characters:
*= every value,= list of values (1,3,5)-= range (1-5)/= step (*/5 = every 5)
Common schedules
| Schedule | Cron expression | Meaning |
|---|---|---|
| Every minute | * * * * * | Runs every 60 seconds |
| Every 5 minutes | */5 * * * * | :00, :05, :10, … |
| Every hour | 0 * * * * | At minute 0 of every hour |
| Daily at midnight | 0 0 * * * | 00:00 every day |
| Daily at 3 AM | 0 3 * * * | 03:00 every day |
| Every Monday at 9 AM | 0 9 * * 1 | Monday 09:00 |
| 1st of every month | 0 0 1 * * | Midnight on the 1st |
| Weekdays at 6 PM | 0 18 * * 1-5 | Mon-Fri 18:00 |
| Every 15 minutes | */15 * * * * | :00, :15, :30, :45 |
Managing crontab
# Edit your crontab
crontab -e
# List your cron jobs
crontab -l
# Remove all your cron jobs
crontab -r
# Edit another user's crontab (requires root)
sudo crontab -u deploy -e
System-wide cron
System cron jobs live in several places:
ls /etc/cron*
Output:
/etc/crontab
/etc/cron.d/
/etc/cron.daily/
/etc/cron.hourly/
/etc/cron.monthly/
/etc/cron.weekly/
/etc/crontabis the system crontab (includes a user field)/etc/cron.d/holds individual cron files from packages/etc/cron.daily/, etc., run scripts at those intervals via anacron
Example 1: Write 3 cron jobs with different schedules
Let’s set up three practical cron jobs:
crontab -e
Add these lines:
# 1. Backup the database every day at 2 AM
0 2 * * * /usr/local/bin/backup-db.sh >> /var/log/backup.log 2>&1
# 2. Clean temp files every Sunday at 4 AM
0 4 * * 0 find /tmp -type f -mtime +7 -delete 2>/dev/null
# 3. Check disk usage every 6 hours and alert if above 80%
0 */6 * * * df -h / | awk 'NR==2 && int($5)>80 {print "DISK ALERT: "$5" used on /"}' | mail -s "Disk Alert" admin@example.com
Verify:
crontab -l
Output:
# 1. Backup the database every day at 2 AM
0 2 * * * /usr/local/bin/backup-db.sh >> /var/log/backup.log 2>&1
# 2. Clean temp files every Sunday at 4 AM
0 4 * * 0 find /tmp -type f -mtime +7 -delete 2>/dev/null
# 3. Check disk usage every 6 hours and alert if above 80%
0 */6 * * * df -h / | awk 'NR==2 && int($5)>80 {print "DISK ALERT: "$5" used on /"}' | mail -s "Disk Alert" admin@example.com
Important cron gotchas:
-
Cron does not load your shell profile. Use absolute paths for commands, or set PATH at the top of your crontab:
PATH=/usr/local/bin:/usr/bin:/bin -
Always redirect output. By default, cron sends output as email. If mail is not configured, output is lost. Always use
>> /path/to/logfile 2>&1. -
Check logs for cron execution:
grep CRON /var/log/syslog | tail -10 -
Test your command manually first. Run it in a minimal environment:
env -i /bin/bash -c '/usr/local/bin/backup-db.sh'
anacron
Cron only runs if the system is on at the scheduled time. If your laptop is off at 2 AM, the backup does not run. anacron solves this by running missed jobs when the system comes back online.
cat /etc/anacrontab
Output:
SHELL=/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
# period delay job-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 delay column is in minutes. It prevents all missed jobs from running simultaneously after boot.
systemd timers
Systemd timers are the modern alternative to cron. They offer:
- Better logging (via journalctl)
- Dependency management (run after network is up)
- Randomized delays (prevent thundering herd)
- Persistent timers (like anacron, run missed jobs)
A systemd timer requires two files:
- A
.servicefile (what to run) - A
.timerfile (when to run it)
Example 2: Convert a cron job to a systemd timer
Let’s convert the daily backup cron job to a systemd timer.
Step 1: Create the service file
sudo cat > /etc/systemd/system/backup-db.service << 'EOF'
[Unit]
Description=Daily database backup
After=network.target postgresql.service
[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup-db.sh
User=root
StandardOutput=journal
StandardError=journal
EOF
Step 2: Create the timer file
sudo cat > /etc/systemd/system/backup-db.timer << 'EOF'
[Unit]
Description=Run database backup daily at 2 AM
[Timer]
OnCalendar=*-*-* 02:00:00
RandomizedDelaySec=300
Persistent=true
[Install]
WantedBy=timers.target
EOF
OnCalendaruses a more readable format than cronRandomizedDelaySec=300adds up to 5 minutes of random delayPersistent=trueruns missed executions (like anacron)
Step 3: Enable and start
# Reload systemd to pick up the new files
sudo systemctl daemon-reload
# Enable the timer (start on boot)
sudo systemctl enable backup-db.timer
# Start the timer now
sudo systemctl start backup-db.timer
# Verify
systemctl list-timers | grep backup
Output:
NEXT LEFT LAST PASSED UNIT ACTIVATES
Thu 2026-05-29 02:00:00 UTC 15h left n/a n/a backup-db.timer backup-db.service
Step 4: Check execution logs
# See when it ran
journalctl -u backup-db.service --since "24 hours ago"
# Manually trigger it to test
sudo systemctl start backup-db.service
# Check status
systemctl status backup-db.service
Common OnCalendar formats
| Format | Meaning |
|---|---|
*-*-* 02:00:00 | Every day at 2 AM |
Mon *-*-* 09:00:00 | Every Monday at 9 AM |
*-*-01 00:00:00 | 1st of every month |
*-*-* *:00/15:00 | Every 15 minutes |
hourly | Every hour |
daily | Every day at midnight |
weekly | Every Monday at midnight |
You can test calendar expressions:
systemd-analyze calendar "Mon *-*-* 09:00:00"
Output:
Original form: Mon *-*-* 09:00:00
Normalized form: Mon *-*-* 09:00:00
Next elapse: Mon 2026-06-01 09:00:00 UTC
(in UTC): Mon 2026-06-01 09:00:00 UTC
From now: 3 days left
When to use cron vs systemd timers
| Feature | Cron | Systemd timers |
|---|---|---|
| Setup complexity | Simple | Two files needed |
| Logging | Manual (redirect output) | Built-in (journalctl) |
| Dependencies | None | Full systemd dependency system |
| Missed runs | Lost (unless using anacron) | Persistent option |
| Random delay | Not built-in | RandomizedDelaySec |
| Monitoring | grep syslog | systemctl list-timers |
For simple tasks on workstations, cron is fine. For production servers, systemd timers are more robust.
What comes next
The final article in the Linux series is Linux security basics for sysadmins, where you will put everything together to harden a Linux server.
Cron jobs with weak file permissions are a common privilege escalation vector. If an attacker can write to a script that runs as root via cron, they own the system.