Running Commands at Specific Times with at and cron
College-Level Course Module | RHEL System Administration
Learning Objectives
1
Schedule one-time tasks with at
Run commands at a specific future time using the at command
2
Schedule recurring tasks with cron
Create crontab entries for jobs that run on a schedule
3
Understand crontab syntax
Master the five-field time specification format
4
Manage scheduled jobs
List, modify, and remove scheduled tasks as a regular user
Scheduling Overview
Linux provides tools to run commands automatically at specified times, either once or on a recurring schedule, without user interaction.
at
One-time execution at a specific date/time. Job runs once and is removed.
cron
Recurring execution on a schedule. Jobs repeat until removed.
anacron
Runs missed cron jobs on systems that aren't always on (laptops).
systemd timers
Modern alternative to cron with more features (system-level).
User vs System: Regular users can schedule personal jobs. System-wide jobs in /etc/cron.* require root access.
The at Command
The at command schedules a one-time job to run at a specified time. Commands are read from stdin or a file.
# Schedule a job interactively[student@server ~]$ at 4:00 PM
warning: commands will be executed using /bin/sh
at> echo "Time to go home!" | mail -s "Reminder" student
at>[Ctrl+D to finish]job 1 at Fri Jan 19 16:00:00 2024# Schedule from command line[student@server ~]$ echo "backup.sh" | at midnight
# Schedule from a file[student@server ~]$ at 9:00 AM tomorrow -f /home/student/scripts/morning.sh
# Check that atd service is running[student@server ~]$ systemctl status atd
Requirement: The atd service must be running. Check with systemctl status atd.
at Time Specifications
at 4:00 PM
Today at 4:00 PM (or tomorrow if past)
at 16:00
24-hour format, same as 4:00 PM
at midnight
12:00 AM tonight
at noon
12:00 PM today
at tomorrow
Same time tomorrow
at now + 2 hours
2 hours from now
at now + 30 minutes
30 minutes from now
at 9:00 AM Jan 20
Specific date and time
at 9:00 AM 1/20/24
Date in MM/DD/YY format
at teatime
4:00 PM (a fun alias!)
# Relative time examples[student@server ~]$ at now + 1 week
[student@server ~]$ at 5 PM + 3 days
[student@server ~]$ at 10:00 AM next Monday
Managing at Jobs
# List pending at jobs[student@server ~]$ atq
1 Fri Jan 19 16:00:00 2024 a student
2 Sat Jan 20 00:00:00 2024 a student
3 Mon Jan 22 09:00:00 2024 a student# View contents of a specific job[student@server ~]$ at -c 1
#!/bin/sh
# atrun uid=1000 gid=1000
...
cd /home/student || { ... }
echo "Time to go home!" | mail -s "Reminder" student# Remove a job by number[student@server ~]$ atrm 2
[student@server ~]$ atq
1 Fri Jan 19 16:00:00 2024 a student
3 Mon Jan 22 09:00:00 2024 a student# Alternative: at -r (same as atrm)[student@server ~]$ at -r 3
Job Numbers: Each at job gets a unique number. Use this number with at -c to view or atrm to remove.
at Access Control
/etc/at.allow
If exists, only users listed can use at. One username per line.
/etc/at.deny
If at.allow doesn't exist, users listed here are denied. Empty = all allowed.
# Check if you can use at[student@server ~]$ at now + 1 minute
warning: commands will be executed using /bin/sh
at># If you get a prompt, you're allowed# If denied:You do not have permission to use at.# View access files (as root)[root@server ~]# cat /etc/at.deny
(empty by default - all users allowed)
Default Behavior: By default, /etc/at.deny exists but is empty, allowing all users to use at. If /etc/at.allow exists, it takes precedence.
Introduction to cron
cron is a daemon that executes scheduled commands at recurring intervals. Jobs are defined in crontab (cron table) files.
User Crontabs
Personal schedules managed with crontab -e. Each user has their own.
System Crontabs
/etc/crontab and /etc/cron.d/* for system-wide jobs (root only).
Cron Directories
/etc/cron.hourly, daily, weekly, monthly for simple scheduling.
crond Service
The daemon that reads crontabs and executes jobs on schedule.
# Verify cron daemon is running[student@server ~]$ systemctl status crond
● crond.service - Command Scheduler
Loaded: loaded
Active: active (running)
crontab Commands
# Edit your crontab (opens in default editor)[student@server ~]$ crontab -e
# List your crontab entries[student@server ~]$ crontab -l
30 8 * * 1-5 /home/student/scripts/morning-report.sh
0 0 * * * /home/student/scripts/daily-backup.sh# Remove your entire crontab (dangerous!)[student@server ~]$ crontab -r
# Remove with confirmation prompt[student@server ~]$ crontab -ri
crontab: really delete student's crontab? y# Root can manage other users' crontabs[root@server ~]# crontab -l -u student
[root@server ~]# crontab -e -u student
⚠ Warning:crontab -r removes ALL your cron jobs without confirmation! Use crontab -ri or just crontab -e to delete specific lines.
Crontab Syntax
30
Minute (0-59)
8
Hour (0-23)
*
Day of Month (1-31)
*
Month (1-12)
1-5
Day of Week (0-7)
/path/to/script.sh
Command
# Format: MIN HOUR DOM MON DOW COMMAND
30 8 * * 1-5 /home/student/scripts/morning-report.sh
# │ │ │ │ └── Day of week: 1-5 (Mon-Fri)# │ │ │ └──── Month: * (every month)# │ │ └────── Day of month: * (every day)# │ └──────── Hour: 8 (8 AM)# └────────── Minute: 30# This job runs at 8:30 AM, Monday through Friday
Crontab Special Characters
Character
Meaning
Example
*
Every value
* * * * * = every minute
,
List of values
0,15,30,45 = these minutes
-
Range of values
9-17 = hours 9 through 17
/
Step values
*/15 = every 15 units
# Every 15 minutes
*/15 * * * * /path/to/script.sh
# At minutes 0, 15, 30, 45 (same as above)
0,15,30,45 * * * * /path/to/script.sh
# Every weekday at 9 AM
0 9 * * 1-5 /path/to/script.sh
# First day of each month at midnight
0 0 1 * * /path/to/script.sh
# Every 2 hours on weekends
0 */2 * * 0,6 /path/to/script.sh
Crontab Examples
# Every minute (good for testing, remove after!)
* * * * * echo "test" >> /tmp/crontest.log
# Daily at 2:30 AM
30 2 * * * /home/student/scripts/daily-backup.sh
# Every Monday at 9 AM
0 9 * * 1 /home/student/scripts/weekly-report.sh
# First of each month at midnight
0 0 1 * * /home/student/scripts/monthly-cleanup.sh
# Every 5 minutes during business hours on weekdays
*/5 9-17 * * 1-5 /home/student/scripts/check-status.sh
# Twice daily at 8 AM and 8 PM
0 8,20 * * * /home/student/scripts/sync-data.sh
# 15th and last day of month (approximated with 28th)
0 0 15,28 * * /home/student/scripts/bimonthly.sh
Testing Tip: Use * * * * * to run every minute while testing, then change to the real schedule once verified.
Cron Special Strings
Instead of five fields, you can use these shortcuts for common schedules:
String
Meaning
Equivalent
@reboot
Run once at startup
N/A
@yearly / @annually
Once a year
0 0 1 1 *
@monthly
Once a month
0 0 1 * *
@weekly
Once a week
0 0 * * 0
@daily / @midnight
Once a day
0 0 * * *
@hourly
Once an hour
0 * * * *
# Start a process at system boot
@reboot /home/student/scripts/start-app.sh
# Daily backup at midnight
@daily /home/student/scripts/backup.sh
# Weekly cleanup on Sunday at midnight
@weekly /home/student/scripts/cleanup.sh
Cron Environment
Important: Cron jobs run with a minimal environment, not your normal login environment! Many commands may not work as expected.
# Cron's default PATH is limited:PATH=/usr/bin:/bin# Set your own PATH and variables in crontab:
PATH=/usr/local/bin:/usr/bin:/bin:/home/student/bin
MAILTO=student@example.com
HOME=/home/student
# Then your jobs:
30 8 * * * morning-report.sh # Now finds scripts in your bin# Or use full paths in commands:
30 8 * * * /home/student/scripts/morning-report.sh
# Or source your profile in the script:#!/bin/bashsource ~/.bash_profile# rest of script...
MAILTO: Set MAILTO=your@email.com to receive job output. Set MAILTO="" to disable mail.
Cron Output and Logging
# By default, all output is mailed to the user# Redirect output to a log file instead:
30 2 * * * /path/to/backup.sh >> /home/student/logs/backup.log 2>&1
# Discard all output:
*/5 * * * * /path/to/check.sh > /dev/null 2>&1
# Log with timestamp:
0 * * * * echo "$(date): Starting hourly job" >> ~/logs/cron.log
# In your crontab, disable mail globally:
MAILTO=""
* * * * * /path/to/job.sh
# Check system cron log for execution records:[student@server ~]$ grep CRON /var/log/cron
Jan 19 08:30:01 server CRON[1234]: (student) CMD (/home/student/scripts/job.sh)
Best Practice: Always redirect output to log files. Include timestamps. Don't rely on cron mail for production jobs.
Cron Access Control
/etc/cron.allow
If exists, only listed users can use cron.
/etc/cron.deny
If cron.allow doesn't exist, listed users are denied.
# Check if you can use cron[student@server ~]$ crontab -e
# If you can edit, you're allowed# If denied:You (student) are not allowed to use this program (crontab)# View access files (as root)[root@server ~]# cat /etc/cron.allow
root
admin
student# Default on RHEL: /etc/cron.deny exists but is empty# This means all users can use cron
System Cron Directories
# Drop-in directories for simple scheduling (root only)
/etc/cron.hourly/ # Scripts run every hour
/etc/cron.daily/ # Scripts run daily
/etc/cron.weekly/ # Scripts run weekly
/etc/cron.monthly/ # Scripts run monthly# List daily cron scripts[student@server ~]$ ls /etc/cron.daily/
logrotate man-db mlocate# System crontab (different format - has username field)[student@server ~]$ cat /etc/crontab
SHELL=/bin/bash
PATH=/sbin:/bin:/usr/sbin:/usr/bin
MAILTO=root
# MIN HOUR DOM MON DOW USER COMMAND
* * * * * root /path/to/script.sh# Additional system cron jobs
/etc/cron.d/ # Drop-in crontab files
Note: These system directories require root to modify. For user jobs, use crontab -e instead.
Practical Example: Backup
# Create a backup script[student@server ~]$ vim ~/scripts/backup-home.sh
#!/bin/bash
# backup-home.sh - Backup home directory
BACKUP_DIR="/home/student/backups"
DATE=$(date +%Y%m%d)
LOG="$BACKUP_DIR/backup.log"
echo "$(date): Starting backup" >> "$LOG"
tar -czf "$BACKUP_DIR/home-$DATE.tar.gz" \
--exclude="$BACKUP_DIR" \
/home/student 2>> "$LOG"
if [ $? -eq 0 ]; then
echo "$(date): Backup completed successfully" >> "$LOG"
else
echo "$(date): Backup FAILED" >> "$LOG"
fi
# Keep only last 7 backups
ls -t "$BACKUP_DIR"/home-*.tar.gz | tail -n +8 | xargs rm -f 2>/dev/null