Schedule Future Tasks
Automate system tasks using cron, anacron, at, and systemd timers. Master job scheduling for recurring maintenance, backups, monitoring, and one-time task execution.
📋 Table of Contents
🎯 Introduction
Task scheduling is essential for system administration. Linux provides multiple tools to automate tasks at specific times or intervals - from simple recurring jobs (backups, log rotation) to complex maintenance schedules. Understanding these tools ensures your systems run smoothly without manual intervention.
In this chapter, you'll master:
- Cron for recurring scheduled tasks
- Systemd timers as a modern alternative to cron
- At for one-time future task execution
- Anacron for systems not running 24/7
- Access control for job scheduling
Scheduling Tools Comparison
| Tool | Type | Best For |
|---|---|---|
| cron | Recurring | Regular intervals (daily backups, hourly checks) |
| systemd timers | Recurring | Modern systems, integrated logging, dependencies |
| at | One-time | Single future execution (reboot at 2am, run report Friday) |
| anacron | Recurring | Desktop/laptop (may not run 24/7) |
On RHEL 9, systemd timers are increasingly preferred over cron for new tasks, but cron remains widely used and is important for the RHCSA exam.
⏰ Cron & Crontab
Cron is the traditional Unix job scheduler that runs commands at specified times and dates. The cron daemon (crond) checks crontab files every minute and executes matching jobs.
Crontab Syntax
Each crontab entry has six fields:
# ┌───────────── minute (0-59)
# │ ┌───────────── hour (0-23)
# │ │ ┌───────────── day of month (1-31)
# │ │ │ ┌───────────── month (1-12)
# │ │ │ │ ┌───────────── day of week (0-6, Sunday=0)
# │ │ │ │ │
# │ │ │ │ │
# * * * * * command to execute
Special Characters
| Character | Meaning | Example |
|---|---|---|
* |
Any value | * * * * * = every minute |
, |
List of values | 0 9,17 * * * = 9am and 5pm |
- |
Range | 0 9-17 * * * = 9am to 5pm |
/ |
Step values | */15 * * * * = every 15 minutes |
Common Crontab Examples
# Run every minute
* * * * * /path/to/command
# Run at 3:30 AM daily
30 3 * * * /usr/local/bin/backup.sh
# Run every hour at minute 0
0 * * * * /usr/bin/check-status.sh
# Run at 2:15 PM on weekdays (Mon-Fri)
15 14 * * 1-5 /usr/bin/report.sh
# Run on the 1st and 15th of every month at midnight
0 0 1,15 * * /usr/local/bin/billing.sh
# Run every 15 minutes
*/15 * * * * /usr/bin/monitor.sh
# Run at 11:45 PM on Sundays
45 23 * * 0 /usr/local/bin/weekly-cleanup.sh
# Run every 6 hours
0 */6 * * * /usr/bin/sync-data.sh
# Run on January 1st at midnight (yearly)
0 0 1 1 * /usr/local/bin/new-year-task.sh
# Run Mon-Fri at 9 AM
0 9 * * 1-5 /usr/local/bin/workday-start.sh
Managing User Crontabs
# Edit your crontab
crontab -e
# List your crontab
crontab -l
# Remove your crontab (with confirmation)
crontab -r
# Remove crontab without confirmation
crontab -r -i
# Edit another user's crontab (as root)
sudo crontab -u username -e
# List another user's crontab
sudo crontab -u username -l
System Crontabs
System-wide cron jobs are stored in different locations:
# System crontab (includes username field)
/etc/crontab
# Sample /etc/crontab:
SHELL=/bin/bash
PATH=/sbin:/bin:/usr/sbin:/usr/bin
MAILTO=root
HOME=/
# minute hour day month dayofweek user command
0 2 * * * root /usr/local/bin/daily-maintenance.sh
# Drop-in directories for scripts
/etc/cron.hourly/ # Run every hour
/etc/cron.daily/ # Run once daily
/etc/cron.weekly/ # Run once weekly
/etc/cron.monthly/ # Run once monthly
# Additional cron jobs
/etc/cron.d/ # Additional crontab files
# Example: Add script to daily cron
sudo cp script.sh /etc/cron.daily/
sudo chmod +x /etc/cron.daily/script.sh
Cron Environment Variables
Set variables at the top of crontab:
# Set shell
SHELL=/bin/bash
# Set PATH
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
# Email output to user
MAILTO=admin@example.com
# Disable email
MAILTO=""
# Set HOME directory
HOME=/home/student
# Your cron jobs below
0 2 * * * /usr/local/bin/backup.sh
Redirecting Cron Output
# Discard all output
0 2 * * * /usr/local/bin/quiet-task.sh > /dev/null 2>&1
# Log output to file
0 2 * * * /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1
# Send errors only
0 2 * * * /usr/local/bin/task.sh > /dev/null
# Separate stdout and stderr
0 2 * * * /usr/local/bin/task.sh >> /var/log/task.log 2>> /var/log/task-error.log
Procedure: Creating a Daily Backup Cron Job
- Create the backup script:
sudo vi /usr/local/bin/daily-backup.sh #!/bin/bash # Daily backup script DATE=$(date +%Y%m%d) tar -czf /backups/home-$DATE.tar.gz /home find /backups -name "home-*.tar.gz" -mtime +7 -delete # Make executable sudo chmod +x /usr/local/bin/daily-backup.sh - Test the script manually:
sudo /usr/local/bin/daily-backup.sh - Edit crontab:
sudo crontab -e - Add cron job (runs at 2 AM daily):
# Daily backup at 2 AM 0 2 * * * /usr/local/bin/daily-backup.sh >> /var/log/backup.log 2>&1 - Verify crontab:
sudo crontab -l - Check cron is running:
sudo systemctl status crond
Cron Access Control
Control who can use cron:
# Allow specific users only
/etc/cron.allow # If exists, only listed users can use cron
/etc/cron.deny # If exists, listed users cannot use cron
# Example: Allow only student and admin
echo "student" | sudo tee /etc/cron.allow
echo "admin" | sudo tee -a /etc/cron.allow
# Example: Deny specific user
echo "baduser" | sudo tee /etc/cron.deny
# If neither file exists, only root can use cron (RHEL default)
# If both exist, cron.allow takes precedence
- Always use absolute paths in cron jobs
- Test scripts manually before adding to cron
- Redirect output or set MAILTO to avoid email spam
- Use meaningful comments in crontab
- For complex schedules, consider systemd timers
⏲️ Systemd Timers
Systemd timers are the modern alternative to cron. They integrate with systemd services, provide better logging, support dependencies, and offer more flexible scheduling options.
Why Use Systemd Timers?
- Integration: Works with systemd services and dependencies
- Logging: Full journald integration for debugging
- Flexibility: Run on boot, relative to other units, with randomization
- Resource control: Can use systemd resource limits
- Accuracy: Better precision for time-critical tasks
Timer Components
A systemd timer requires two files:
- Service unit (.service) - Defines what to run
- Timer unit (.timer) - Defines when to run it
Creating a Systemd Timer
Example: Daily Cleanup Timer
- Create the service file:
sudo vi /etc/systemd/system/cleanup.service [Unit] Description=Daily Cleanup Task Wants=cleanup.timer [Service] Type=oneshot ExecStart=/usr/local/bin/cleanup.sh [Install] WantedBy=multi-user.target - Create the timer file:
sudo vi /etc/systemd/system/cleanup.timer [Unit] Description=Daily Cleanup Timer Requires=cleanup.service [Timer] OnCalendar=daily OnCalendar=*-*-* 02:00:00 Persistent=true [Install] WantedBy=timers.target - Reload systemd:
sudo systemctl daemon-reload - Enable and start the timer:
sudo systemctl enable cleanup.timer sudo systemctl start cleanup.timer - Verify the timer:
sudo systemctl status cleanup.timer sudo systemctl list-timers cleanup.timer
Timer Scheduling Syntax
OnCalendar accepts various formats:
# Simple intervals
OnCalendar=hourly # Every hour
OnCalendar=daily # Every day at midnight
OnCalendar=weekly # Every week on Monday
OnCalendar=monthly # Every month on the 1st
OnCalendar=yearly # Every year on January 1st
# Specific times
OnCalendar=*-*-* 02:30:00 # Daily at 2:30 AM
OnCalendar=Mon *-*-* 09:00:00 # Mondays at 9 AM
OnCalendar=Mon..Fri *-*-* 18:00:00 # Weekdays at 6 PM
# Multiple times
OnCalendar=*-*-* 06:00:00 # 6 AM
OnCalendar=*-*-* 18:00:00 # 6 PM
# Every 15 minutes
OnCalendar=*:0/15
# Test calendar expression
systemd-analyze calendar "Mon..Fri *-*-* 09:00:00"
Timer Options
| Option | Description |
|---|---|
OnCalendar= |
Real-time (wall clock) scheduling |
OnBootSec= |
Time after boot |
OnActiveSec= |
Time after timer activation |
OnUnitActiveSec= |
Time after service last ran |
Persistent= |
Run missed timers on boot |
AccuracySec= |
Accuracy window (default 1min) |
RandomizedDelaySec= |
Random delay before execution |
Managing Timers
# List all timers
systemctl list-timers
# List all timers (including inactive)
systemctl list-timers --all
# Check timer status
systemctl status cleanup.timer
# View timer details
systemctl show cleanup.timer
# Check when timer will run next
systemctl list-timers cleanup.timer
# Manually trigger the service
sudo systemctl start cleanup.service
# View service logs
sudo journalctl -u cleanup.service
# View timer logs
sudo journalctl -u cleanup.timer
Example: Hourly System Check
# /etc/systemd/system/syscheck.service
[Unit]
Description=Hourly System Health Check
[Service]
Type=oneshot
ExecStart=/usr/local/bin/check-system.sh
StandardOutput=journal
# /etc/systemd/system/syscheck.timer
[Unit]
Description=Run system check every hour
[Timer]
OnCalendar=hourly
OnBootSec=5min
AccuracySec=1s
[Install]
WantedBy=timers.target
# Enable and start
sudo systemctl daemon-reload
sudo systemctl enable --now syscheck.timer
📅 At & Batch Commands
The at command schedules one-time tasks to run at a specific time in the future.
Unlike cron (recurring), at jobs run once and are then removed.
Installing At
# Install at package
sudo dnf install at
# Start and enable atd service
sudo systemctl enable --now atd
# Verify service
sudo systemctl status atd
Scheduling Jobs with At
# Run job at specific time
at 10:00 PM
> /usr/local/bin/backup.sh
>
# Run job tomorrow at 9 AM
at 9am tomorrow
> echo "Daily report" | mail -s "Report" admin@example.com
>
# Run job at specific date and time
at 2:30 PM December 25
> /usr/local/bin/christmas-task.sh
>
# Run job in 30 minutes
at now + 30 minutes
> /usr/local/bin/quick-task.sh
>
# Run job in 2 hours
at now + 2 hours
> systemctl restart httpd
>
# Run job next week
at 10am next week
> /usr/local/bin/weekly-report.sh
>
# Run job from file
at 2am tomorrow -f /usr/local/bin/script.sh
# Run job using here document
at now + 5 minutes <> /var/log/tasks.log
/usr/local/bin/my-task.sh
echo "Task completed at \$(date)" >> /var/log/tasks.log
EOF
At Time Specifications
| Format | Example |
|---|---|
| HH:MM | 14:30, 10:00 PM |
| midnight, noon, teatime | noon tomorrow |
| now + time | now + 30 minutes |
| today, tomorrow | 10am tomorrow |
| MMDDYY | 122524 (Dec 25, 2024) |
Managing At Jobs
# List scheduled jobs
atq
# or
at -l
# Output example:
# 1 Thu Nov 14 22:00:00 2025 a student
# 2 Fri Nov 15 09:00:00 2025 a student
# View job contents
at -c 1 # Show job number 1
# Remove job
atrm 1 # Remove job number 1
# or
at -d 1
# Remove all jobs for current user
atrm $(atq | cut -f1)
# Remove specific user's jobs (as root)
sudo atrm $(atq -u username | cut -f1)
Batch Command
Similar to at, but runs only when system load is low:
# Run when load average drops below 1.5 (default)
batch
> /usr/local/bin/resource-intensive-task.sh
>
# Batch jobs appear in atq
atq
At Access Control
# Control who can use at
/etc/at.allow # If exists, only listed users can use at
/etc/at.deny # If exists, listed users cannot use at
# Example: Allow specific users
echo "student" | sudo tee /etc/at.allow
echo "admin" | sudo tee -a /etc/at.allow
# If neither file exists, only root can use at
Procedure: Schedule System Reboot
- Schedule reboot for 2 AM:
sudo at 2am tomorrow at> systemctl reboot at>job 3 at Fri Nov 15 02:00:00 2025 - Verify scheduled job:
sudo atq - View job details:
sudo at -c 3 - To cancel if needed:
sudo atrm 3
🔄 Anacron
Anacron runs commands periodically but doesn't assume the system is always on. Unlike cron (which skips missed jobs), anacron ensures jobs run eventually - perfect for desktops and laptops.
Anacron vs Cron
| Feature | Cron | Anacron |
|---|---|---|
| Minimum interval | 1 minute | 1 day |
| Missed jobs | Skipped | Run on next boot |
| Best for | Servers (always on) | Desktops/laptops |
Anacron Configuration
Main configuration file: /etc/anacrontab
# /etc/anacrontab example
SHELL=/bin/sh
PATH=/sbin:/bin:/usr/sbin:/usr/bin
MAILTO=root
RANDOM_DELAY=45 # Maximum random delay in minutes
START_HOURS_RANGE=3-22 # Only run between 3am-10pm
# period delay job-identifier command
1 5 cron.daily nice run-parts /etc/cron.daily
7 25 cron.weekly nice run-parts /etc/cron.weekly
@monthly 45 cron.monthly nice run-parts /etc/cron.monthly
# Format:
# period = days between runs
# delay = minutes to wait before running
# identifier = unique job name
# command = command to execute
How Anacron Works
- Anacron checks timestamp files in
/var/spool/anacron/ - If a job hasn't run in its specified period, it's scheduled
- Jobs wait for their delay period, then execute
- Timestamp is updated after successful execution
Managing Anacron
# Manually run anacron (checks all jobs)
sudo anacron
# Force run all jobs now (ignore timestamps)
sudo anacron -f
# Run specific job
sudo anacron -f cron.daily
# Test mode (don't actually run)
sudo anacron -d -n
# Update timestamps without running
sudo anacron -u
# Check timestamp files
ls -l /var/spool/anacron/
On RHEL, anacron is integrated with cron. The cron.daily, cron.weekly, and cron.monthly directories are managed by anacron to ensure they run even if the system was off.
📝 Practice Questions
Question 1: What does this crontab entry do? 30 2 * * 1-5
Format is minute(30) hour(2) day(*) month(*) weekday(1-5 = Mon-Fri). So this runs at 2:30 AM Monday through Friday.
Question 2: How do you schedule a one-time job to run in 4 hours?
The at command is for one-time future execution. Usage: at now + 4 hours, then enter commands, press Ctrl+D. Cron is for recurring tasks.
Question 3: What file controls which users can create cron jobs?
If /etc/cron.allow exists, only listed users can use cron. If /etc/cron.deny exists, listed users cannot use cron. If neither exists, only root can use cron.
Question 4: Which command lists all pending at jobs?
Both 'at -l' and 'atq' list scheduled at jobs. Use 'atrm jobnumber' to remove a job. crontab -l lists cron jobs, not at jobs.
Question 5: What's the advantage of systemd timers over cron?
Systemd timers integrate with journald for logging, support service dependencies, offer more scheduling flexibility (OnBootSec, Persistent, etc.), and are the modern approach on RHEL 9.