Automated task scheduling is the backbone of server maintenance: backups run at 3 AM, log cleanup happens daily, SSL certificates renew before they expire, and databases are vacuumed weekly. On RHEL 9, there are three scheduling mechanisms each suited to different scenarios. cron is the classic daemon that runs jobs on a schedule defined by five time fields (minute, hour, day, month, weekday) and has been the standard since the 1970s. anacron complements cron for machines that are not always on — it runs missed jobs on the next startup with configurable delays, making it ideal for daily/weekly/monthly tasks on servers that may be rebooted or have maintenance windows. Systemd timers (covered in the systemd guide) are the modern alternative for service-integrated jobs with better logging and dependency management. This guide focuses on cron and anacron with practical examples for real administration tasks.
Prerequisites
- RHEL 9 server with root or sudo access
croniepackage installed (default on RHEL 9)
Step 1 — Install and Enable cronie
cronie provides both crond and anacron on RHEL 9:
dnf install -y cronie cronie-anacron
systemctl enable --now crond
systemctl status crond
Step 2 — Understand Crontab Syntax
A crontab line has 6 fields: 5 time fields followed by the command:
# .---------------- minute (0-59)
# | .------------- hour (0-23)
# | | .---------- day of month (1-31)
# | | | .------- month (1-12 or jan-dec)
# | | | | .---- day of week (0-7, both 0 and 7 = Sunday, or sun-sat)
# | | | | |
# * * * * * command to execute
Common patterns:
0 2 * * * # Every day at 2:00 AM
*/15 * * * * # Every 15 minutes
0 9-17 * * 1-5 # On the hour, 9 AM to 5 PM, Monday to Friday
0 0 1 * * # First day of every month at midnight
0 0 * * 0 # Every Sunday at midnight
@reboot # Once at system startup
@daily # Same as: 0 0 * * *
@weekly # Same as: 0 0 * * 0
@monthly # Same as: 0 0 1 * *
@hourly # Same as: 0 * * * *
Step 3 — Manage User Crontabs
Each user has their own crontab stored in /var/spool/cron/:
# Open your own crontab in an editor
crontab -e
# List your current crontab
crontab -l
# Remove your entire crontab (use with caution)
crontab -r
# Edit another user's crontab (as root)
crontab -e -u appuser
# List another user's crontab
crontab -l -u appuser
Step 4 — Practical Crontab Examples
# Run a backup script at 2:30 AM daily
30 2 * * * /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1
# Rotate custom logs every day at midnight
0 0 * * * /usr/sbin/logrotate -f /etc/logrotate.d/myapp >> /var/log/logrotate-manual.log 2>&1
# Clear tmp files older than 7 days every Sunday
0 3 * * 0 find /tmp -type f -mtime +7 -delete
# Renew SSL certificate monthly and reload Nginx
0 4 1 * * certbot renew --quiet && systemctl reload nginx
# Check disk usage and alert if above 90%
*/10 * * * * df -h / | awk 'NR==2{if(substr($5,1,length($5)-1)+0>90)print "DISK ALERT: " $5 " used on /"}' | mail -s "Disk Alert" [email protected]
# rsync incremental backup every night
0 1 * * * rsync -az --delete /var/www/html/ [email protected]:/backups/html/$(date +%Y-%m-%d)/
Always redirect both stdout and stderr: >> /path/to/log 2>&1 captures all output. Without redirection, cron emails output to the local MAILTO address.
Step 5 — Configure Cron Environment Variables
Cron runs with a minimal environment — notably, PATH is very limited compared to an interactive shell. Always use full paths for commands in crontabs:
# At the top of a crontab, set environment variables
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
[email protected]
HOME=/root
# Now commands can use short names from PATH
0 2 * * * backup.sh >> /var/log/backup.log 2>&1
Step 6 — System-Wide Cron Jobs
System-wide cron jobs can be placed in several locations:
/etc/crontab— system crontab with an extra user field before the command/etc/cron.d/— drop-in directory with same format as/etc/crontab/etc/cron.hourly/,/etc/cron.daily/,/etc/cron.weekly/,/etc/cron.monthly/— scripts placed here run at the named frequency
# System crontab format: add username field between time fields and command
# /etc/cron.d/myapp-cleanup
SHELL=/bin/bash
PATH=/sbin:/bin:/usr/sbin:/usr/bin
0 3 * * 0 appuser /opt/myapp/bin/weekly-cleanup.sh >> /var/log/myapp-cleanup.log 2>&1
# Place a script in cron.daily (must be executable, no .sh extension required)
cp /opt/scripts/daily-health-check.sh /etc/cron.daily/daily-health-check
chmod +x /etc/cron.daily/daily-health-check
Step 7 — Configure anacron for Missed Jobs
anacron ensures that daily/weekly/monthly jobs are not permanently skipped if the server was off during the scheduled time. The configuration is at /etc/anacrontab:
cat /etc/anacrontab
# /etc/anacrontab
# period delay job-identifier command
# period: days between runs
# delay: minutes to wait after boot before running
SHELL=/bin/bash
PATH=/sbin:/bin:/usr/sbin:/usr/bin
MAILTO=root
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
The delay field staggers jobs so they do not all run simultaneously on boot. The nice prefix runs them at lower CPU priority to avoid impacting server performance.
Step 8 — Restrict Cron Access
# Allow only specific users to use cron
vi /etc/cron.allow
# Add usernames, one per line: only these users can create crontabs
# Deny specific users from using cron
vi /etc/cron.deny
# Add usernames to block
# If cron.allow exists, only listed users can use cron (cron.deny ignored)
# If only cron.deny exists, all users except listed ones can use cron
Step 9 — Test and Debug Cron Jobs
# View cron execution log
grep CRON /var/log/cron | tail -30
# Or via journald
journalctl -u crond --since today
# Test a script runs correctly with the same limited environment as cron
env -i HOME=/root LOGNAME=root PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin /bin/bash /opt/scripts/backup.sh
# Run anacron manually to test missed jobs
anacron -f -n # -f: force run, -n: no delays
Verification Checklist
# crond is running
systemctl status crond
# Your crontab
crontab -l
# System cron jobs
ls /etc/cron.d/ /etc/cron.daily/ /etc/cron.weekly/ /etc/cron.monthly/
# Recent cron activity
grep CRON /var/log/cron | tail -20
Troubleshooting
- Job not running at expected time — check the server timezone:
timedatectl. Cron uses local time. Also check/var/log/cronto confirm cron received and ran the job. - Script runs manually but not via cron — PATH problem. Use full absolute paths for all commands in cron scripts, or set PATH at the top of the crontab.
- No output from cron job — if no MAILTO is set, output goes to the local mail spool. Either set
[email protected]or redirect all output to a log file with>> /var/log/job.log 2>&1.
Conclusion
Your RHEL 9 server now has a complete task scheduling setup: user crontabs for per-user jobs, system cron in /etc/cron.d/ and the frequency directories, anacron for catching missed jobs, correct PATH configuration in crontabs, and comprehensive logging. The key best practices — absolute paths, output redirection, and dry-running scripts in the cron environment before scheduling — will save you hours of debugging.
Next steps: How to Monitor Disk Usage with df, du, lsblk and ncdu on RHEL 9, How to Use journalctl for Systemd Log Analysis on RHEL 9, and How to Use rsync for Efficient File Synchronisation on RHEL 9.