RED HAT ENTERPRISE LINUX

System Scheduling

Scheduling Recurring Tasks for Daemons and OS Functions

College-Level Course Module | RHEL System Administration

Learning Objectives

1
Understand systemd timers

Create and manage timer units for scheduled service activation

2
Configure system cron jobs

Use /etc/crontab, /etc/cron.d/, and cron directories for system tasks

3
Use anacron for resilient scheduling

Ensure jobs run on systems that aren't continuously powered

4
Manage temporary files with systemd-tmpfiles

Configure automatic cleanup of temporary and transient files

System Scheduling Overview

System scheduling runs recurring tasks that maintain OS health: log rotation, temporary file cleanup, security updates, database maintenance, and service health checks.

systemd Timers

Modern approach. Timer unit activates a service unit on schedule. Full systemd integration.

System Cron

/etc/crontab and /etc/cron.d/. Traditional scheduling with username field.

anacron

Catches up on missed jobs. Used for daily/weekly/monthly system tasks.

systemd-tmpfiles

Manages temporary files. Creates, cleans, and removes files/directories.

Root Required: System scheduling mechanisms require administrator privileges to configure.

systemd Timers Introduction

A systemd timer is a unit that activates another unit (usually a service) based on time. Timers are the systemd replacement for cron jobs.

# List all timers
[root@server ~]# systemctl list-timers
NEXT                        LEFT          LAST                        PASSED       UNIT                         ACTIVATES
Sun 2024-01-21 00:00:00 EST 5h 30min left Sat 2024-01-20 00:00:00 EST 18h ago      logrotate.timer              logrotate.service
Sun 2024-01-21 00:00:00 EST 5h 30min left Sat 2024-01-20 00:00:00 EST 18h ago      systemd-tmpfiles-clean.timer systemd-tmpfiles-clean.service
Mon 2024-01-22 00:00:00 EST 1 day left    Mon 2024-01-15 00:00:00 EST 5 days ago   fstrim.timer                 fstrim.service

# Include inactive timers
[root@server ~]# systemctl list-timers --all

# Show timer status
[root@server ~]# systemctl status logrotate.timer
● logrotate.timer - Daily rotation of log files
     Loaded: loaded (/usr/lib/systemd/system/logrotate.timer; enabled)
     Active: active (waiting)

Timer Unit Structure

# /etc/systemd/system/backup.timer
[Unit]
Description=Daily Backup Timer

[Timer]
OnCalendar=*-*-* 02:00:00
Persistent=true

[Install]
WantedBy=timers.target
# /etc/systemd/system/backup.service
[Unit]
Description=System Backup Service

[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup.sh
# Enable and start the timer
[root@server ~]# systemctl daemon-reload
[root@server ~]# systemctl enable --now backup.timer

# Verify it's scheduled
[root@server ~]# systemctl list-timers backup.timer

OnCalendar Syntax

The OnCalendar directive uses a flexible calendar event syntax: DayOfWeek Year-Month-Day Hour:Minute:Second

*-*-* 02:00:00
Every day at 2:00 AM
Mon *-*-* 09:00:00
Every Monday at 9:00 AM
*-*-01 00:00:00
First of every month at midnight
*-01-01 00:00:00
January 1st each year
Mon..Fri *-*-* 08:00:00
Weekdays at 8:00 AM
*-*-* *:00,30:00
Every 30 minutes
daily
Shorthand for *-*-* 00:00:00
weekly
Shorthand for Mon *-*-* 00:00:00
# Test calendar expressions with systemd-analyze
[root@server ~]# systemd-analyze calendar "Mon..Fri *-*-* 09:00:00"
  Original form: Mon..Fri *-*-* 09:00:00
Normalized form: Mon..Fri *-*-* 09:00:00
    Next elapse: Mon 2024-01-22 09:00:00 EST
       (in UTC): Mon 2024-01-22 14:00:00 UTC
       From now: 1 day 3h left

Monotonic Timers

Monotonic timers trigger relative to a reference point (boot time, unit activation, etc.) rather than wall-clock time.

DirectiveTriggers Relative ToExample
OnBootSec=System bootOnBootSec=5min
OnStartupSec=systemd start (similar to boot)OnStartupSec=10min
OnActiveSec=Timer activationOnActiveSec=1h
OnUnitActiveSec=Last activation of triggered unitOnUnitActiveSec=30min
OnUnitInactiveSec=Last deactivation of triggered unitOnUnitInactiveSec=1h
# Timer that runs 5 minutes after boot, then every hour
[Timer]
OnBootSec=5min
OnUnitActiveSec=1h

# Timer that runs 15 minutes after boot only
[Timer]
OnBootSec=15min

Timer Options

[Timer]
# When to run
OnCalendar=*-*-* 02:00:00

# Run if missed while system was off
Persistent=true

# Add randomized delay (avoid thundering herd)
RandomizedDelaySec=1h

# Accuracy (wake from sleep if needed)
AccuracySec=1min

# Activate different unit than default
Unit=my-special.service
Persistent=true: If the system was off when the timer should have fired, run the job when the system starts. Critical for maintenance tasks!
RandomizedDelaySec: Prevents all systems from running the same job simultaneously. Important for distributed environments.

Creating a Custom Timer

# Create the service unit
[root@server ~]# vim /etc/systemd/system/cleanup.service
[Unit]
Description=Weekly Cleanup Service

[Service]
Type=oneshot
ExecStart=/usr/local/bin/weekly-cleanup.sh
StandardOutput=journal
StandardError=journal
# Create the timer unit
[root@server ~]# vim /etc/systemd/system/cleanup.timer
[Unit]
Description=Weekly Cleanup Timer

[Timer]
OnCalendar=Sun *-*-* 03:00:00
Persistent=true
RandomizedDelaySec=30min

[Install]
WantedBy=timers.target
# Enable and start
[root@server ~]# systemctl daemon-reload
[root@server ~]# systemctl enable --now cleanup.timer
[root@server ~]# systemctl list-timers cleanup.timer

Managing Timers

# List all timers (active and inactive)
[root@server ~]# systemctl list-timers --all

# Check timer status
[root@server ~]# systemctl status cleanup.timer

# Enable timer to start at boot
[root@server ~]# systemctl enable cleanup.timer

# Start timer now
[root@server ~]# systemctl start cleanup.timer

# Stop timer
[root@server ~]# systemctl stop cleanup.timer

# Disable timer (won't start at boot)
[root@server ~]# systemctl disable cleanup.timer

# Manually trigger the associated service
[root@server ~]# systemctl start cleanup.service

# View logs from timer-triggered service
[root@server ~]# journalctl -u cleanup.service

# View recent timer activity
[root@server ~]# journalctl -u cleanup.timer --since "1 week ago"

System Crontab

The system crontab /etc/crontab has an extra field specifying which user runs each job. Only root can edit this file.

[root@server ~]# cat /etc/crontab
SHELL=/bin/bash
PATH=/sbin:/bin:/usr/sbin:/usr/bin
MAILTO=root

# For details see man 4 crontabs

# Example of job definition:
# .---------------- minute (0 - 59)
# |  .------------- hour (0 - 23)
# |  |  .---------- day of month (1 - 31)
# |  |  |  .------- month (1 - 12) OR jan,feb,mar,apr ...
# |  |  |  |  .---- day of week (0 - 6) (Sunday=0 or 7) OR sun,mon,tue...
# |  |  |  |  |
# *  *  *  *  * user-name  command to be executed

# MIN HOUR DOM MON DOW USER    COMMAND
  0   3    *   *   *   root    /usr/local/bin/backup.sh
 30   4    *   *   0   root    /usr/local/bin/weekly-maintenance.sh
Note: The username field between day-of-week and command is what distinguishes /etc/crontab from user crontabs.

The /etc/cron.d/ Directory

Drop-in crontab files in /etc/cron.d/ use the same format as /etc/crontab (with username field). Packages install their cron jobs here.

# List cron.d contents
[root@server ~]# ls -la /etc/cron.d/
-rw-r--r--. 1 root root  128 Jan 10 10:00 0hourly
-rw-r--r--. 1 root root  108 Jan 10 10:00 raid-check
-rw-r--r--. 1 root root  121 Jan 10 10:00 sysstat

# View a cron.d file
[root@server ~]# cat /etc/cron.d/sysstat
# Run system activity accounting tool every 10 minutes
*/10 * * * * root /usr/lib64/sa/sa1 1 1 -S DISK
# Generate daily summary at 23:53
53 23 * * * root /usr/lib64/sa/sa2 -A

# Create a custom cron.d file
[root@server ~]# vim /etc/cron.d/myapp
# MyApp maintenance
0 2 * * * root /opt/myapp/bin/maintenance.sh
Advantages: Each application can have its own file. Removing the package removes its cron file. Easier to manage than one big crontab.

Cron Directories

/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
[root@server ~]# ls -la /etc/cron.daily/
-rwxr-xr-x. 1 root root  219 Jan 10 logrotate
-rwxr-xr-x. 1 root root  618 Jan 10 man-db
-rwxr-xr-x. 1 root root  608 Jan 10 mlocate

# Add a custom daily script
[root@server ~]# vim /etc/cron.daily/custom-cleanup
[root@server ~]# chmod +x /etc/cron.daily/custom-cleanup

# Scripts must be executable!
[root@server ~]# ls -l /etc/cron.daily/custom-cleanup
-rwxr-xr-x. 1 root root 156 Jan 20 15:00 custom-cleanup
Important: Scripts must be executable (chmod +x) and should not have file extensions like .sh (run-parts skips files with dots).

anacron

anacron runs jobs that were scheduled but missed because the system was off. It ensures daily, weekly, and monthly jobs eventually run.

[root@server ~]# cat /etc/anacrontab
# /etc/anacrontab: configuration file for anacron

SHELL=/bin/sh
PATH=/sbin:/bin:/usr/sbin:/usr/bin
MAILTO=root
RANDOM_DELAY=45
START_HOURS_RANGE=3-22

#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

# Check when jobs last ran
[root@server ~]# ls -la /var/spool/anacron/
-rw-------. 1 root root 9 Jan 20 03:15 cron.daily
-rw-------. 1 root root 9 Jan 14 03:30 cron.weekly
-rw-------. 1 root root 9 Jan  1 03:45 cron.monthly

systemd-tmpfiles

systemd-tmpfiles creates, deletes, and cleans up volatile and temporary files and directories based on configuration files.

# Run tmpfiles cleanup manually
[root@server ~]# systemd-tmpfiles --clean

# Create configured files/directories
[root@server ~]# systemd-tmpfiles --create

# View the cleanup timer
[root@server ~]# systemctl status systemd-tmpfiles-clean.timer
● systemd-tmpfiles-clean.timer - Daily Cleanup of Temporary Directories
     Active: active (waiting)
    Trigger: Sun 2024-01-21 00:00:00 EST; 5h left

# Configuration file locations
/etc/tmpfiles.d/*.conf          # Local admin configuration
/run/tmpfiles.d/*.conf          # Runtime configuration
/usr/lib/tmpfiles.d/*.conf      # Package defaults

tmpfiles.d Configuration

# Format: Type Path Mode User Group Age Argument

# Create directory with permissions
d /run/myapp 0755 myapp myapp -

# Create directory, clean files older than 10 days
D /var/tmp/myapp 0755 root root 10d

# Create empty file
f /var/log/myapp.log 0644 myapp myapp -

# Create symlink
L /var/run/myapp - - - - /run/myapp

# Remove path recursively
R /tmp/oldcache - - - - -

# Clean files older than 30 days (Age field)
e /var/cache/myapp - - - 30d
# Create custom tmpfiles configuration
[root@server ~]# vim /etc/tmpfiles.d/myapp.conf
# MyApp tmpfiles configuration
d /run/myapp 0755 myapp myapp -
f /run/myapp/app.pid 0644 myapp myapp -
D /var/tmp/myapp 0750 myapp myapp 7d

# Test the configuration
[root@server ~]# systemd-tmpfiles --create /etc/tmpfiles.d/myapp.conf

Comparing Methods

Featuresystemd Timerscron/anacron
Dependency handlingFull systemd dependenciesNone
Loggingjournald integrationMail or manual redirect
Missed job handlingPersistent=trueanacron
Random delayRandomizedDelaySecRANDOM_DELAY in anacrontab
Resource controlFull cgroup supportLimited (nice)
Calendar syntaxOnCalendar (readable)5-field format
Boot-relativeOnBootSec, OnStartupSec@reboot only
Testingsystemd-analyze calendarLimited
Status visibilitysystemctl statusNo built-in
FamiliarityNewer, learning curveTraditional, well-known
Recommendation: Use systemd timers for new system services. Use cron for simpler tasks or when compatibility with non-systemd systems matters.

Common System Timers

[root@server ~]# systemctl list-timers
NEXT                        LEFT       LAST                        PASSED    UNIT                         ACTIVATES
Sun 2024-01-21 00:00:00 EST 4h left    Sat 2024-01-20 00:00:00 EST 19h ago   logrotate.timer              logrotate.service
Sun 2024-01-21 00:00:00 EST 4h left    Sat 2024-01-20 00:00:00 EST 19h ago   systemd-tmpfiles-clean.timer systemd-tmpfiles-clean.service
Mon 2024-01-22 00:00:00 EST 1 day left Mon 2024-01-15 00:00:00 EST 5 days    fstrim.timer                 fstrim.service
Sun 2024-01-21 06:00:00 EST 10h left   Sat 2024-01-20 06:12:00 EST 13h ago   dnf-makecache.timer          dnf-makecache.service
TimerPurposeDefault Schedule
logrotate.timerRotate log files to prevent disk fillDaily
systemd-tmpfiles-clean.timerClean temporary directoriesDaily
fstrim.timerDiscard unused SSD blocksWeekly
dnf-makecache.timerRefresh package metadataHourly (with conditions)
unbound-anchor.timerUpdate DNSSEC root anchorMonthly

Troubleshooting

# Check if timers/cron daemon running
[root@server ~]# systemctl status crond
[root@server ~]# systemctl list-timers --all

# View systemd timer logs
[root@server ~]# journalctl -u backup.timer
[root@server ~]# journalctl -u backup.service --since "1 day ago"

# View cron logs
[root@server ~]# grep CRON /var/log/cron
[root@server ~]# journalctl -u crond --since "1 hour ago"

# Test timer calendar expression
[root@server ~]# systemd-analyze calendar "Mon *-*-* 09:00:00"

# Manually run a timer's service
[root@server ~]# systemctl start backup.service

# Verify cron.d file permissions
[root@server ~]# ls -la /etc/cron.d/
# Files must be owned by root, not group/world writable

# Check cron directory scripts are executable
[root@server ~]# ls -la /etc/cron.daily/

Best Practices

✓ Do

  • Use systemd timers for new services
  • Set Persistent=true for maintenance jobs
  • Use RandomizedDelaySec in clusters
  • Log all job output to journald or files
  • Test scripts manually before scheduling
  • Use /etc/cron.d/ over /etc/crontab
  • Monitor job success/failure
  • Document what each job does

✗ Don't

  • Schedule intensive jobs during peak hours
  • Forget dependencies (network, mounts)
  • Use .sh extension in cron directories
  • Ignore failed job notifications
  • Hardcode paths without testing
  • Schedule conflicting jobs at same time
  • Forget to enable timers after creating
  • Assume the environment is your shell
Security: System scheduled jobs run as root by default. Use dedicated service accounts when possible. Validate all inputs.

Key Takeaways

1

systemd Timers: Modern scheduling with .timer + .service units. OnCalendar syntax, Persistent=true, journald logging

2

System Cron: /etc/crontab and /etc/cron.d/ with username field. Cron directories for simple daily/weekly/monthly jobs

3

anacron: Catches missed jobs on non-24/7 systems. Runs overdue daily/weekly/monthly jobs at startup

4

systemd-tmpfiles: Creates and cleans temporary files. Configure in /etc/tmpfiles.d/*.conf

Next: Managing Temporary Files