Search…
Linux from Scratch · Part 14

Cron jobs and task scheduling

In this series (15 parts)
  1. What is Linux and how it differs from other OSes
  2. Installing Linux and setting up your environment
  3. The Linux filesystem explained
  4. Users, groups, and permissions
  5. Essential command line tools
  6. Shell scripting fundamentals
  7. Processes and job control
  8. Standard I/O, pipes, and redirection
  9. The Linux networking stack
  10. Package management and software installation
  11. Disk management and filesystems
  12. Logs and system monitoring
  13. SSH and remote access
  14. Cron jobs and task scheduling
  15. 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

ScheduleCron expressionMeaning
Every minute* * * * *Runs every 60 seconds
Every 5 minutes*/5 * * * *:00, :05, :10, …
Every hour0 * * * *At minute 0 of every hour
Daily at midnight0 0 * * *00:00 every day
Daily at 3 AM0 3 * * *03:00 every day
Every Monday at 9 AM0 9 * * 1Monday 09:00
1st of every month0 0 1 * *Midnight on the 1st
Weekdays at 6 PM0 18 * * 1-5Mon-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/crontab is 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:

  1. 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
  2. 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.

  3. Check logs for cron execution:

    grep CRON /var/log/syslog | tail -10
  4. 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:

  1. A .service file (what to run)
  2. A .timer file (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
  • OnCalendar uses a more readable format than cron
  • RandomizedDelaySec=300 adds up to 5 minutes of random delay
  • Persistent=true runs 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

FormatMeaning
*-*-* 02:00:00Every day at 2 AM
Mon *-*-* 09:00:00Every Monday at 9 AM
*-*-01 00:00:001st of every month
*-*-* *:00/15:00Every 15 minutes
hourlyEvery hour
dailyEvery day at midnight
weeklyEvery 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

FeatureCronSystemd timers
Setup complexitySimpleTwo files needed
LoggingManual (redirect output)Built-in (journalctl)
DependenciesNoneFull systemd dependency system
Missed runsLost (unless using anacron)Persistent option
Random delayNot built-inRandomizedDelaySec
Monitoringgrep syslogsystemctl 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.

Start typing to search across all content
navigate Enter open Esc close