Skip to content

Scheduled Tasks (Timers)

systemd timers are a modern alternative to cron, offering more precise scheduling control, seamless log integration, and dependency management capabilities. On CentOS/AlmaLinux/Rocky Linux, an increasing number of system tasks have been migrated from cron to systemd timers.

Featuresystemd TimerCron
Log integrationAutomatically integrates with journald; viewable via journalctlRequires manual log redirection
Dependency managementSupports After=, Requires=, and other dependency declarationsNo dependency management
Missed executionsSupports Persistent=true; missed tasks run at next startupMissed tasks are lost
Resource controlCan set CPU, memory, and IO limitsNo resource control
PrecisionSupports microsecond-level precisionMinimum granularity is 1 minute
Boot delaySupports OnBootSec= and other relative time triggersNot supported
Random delaySupports RandomizedDelaySec= to spread loadNot supported
ManagementUnified management via systemctlRequires editing crontab files

systemd timers work in pairs: each .timer unit corresponds to a .service unit with the same name. When the timer triggers, systemd automatically starts the corresponding service unit.

For example:

  • backup.timer — Defines the trigger schedule
  • backup.service — Defines the task to execute

Triggered based on relative time, suitable for “how long since a certain event” scenarios:

DirectiveDescription
OnBootSec=Trigger after a specified time since system boot
OnActiveSec=Trigger after a specified time since the timer was activated
OnStartupSec=Trigger after a specified time since systemd started
OnUnitActiveSec=Trigger after a specified time since the corresponding service was last activated
OnUnitInactiveSec=Trigger after a specified time since the corresponding service last completed

Triggered based on absolute time, similar to cron scheduling:

DirectiveDescription
OnCalendar=Trigger according to a calendar time expression

OnCalendar= uses the following format:

DayOfWeek Year-Month-Day Hour:Minute:Second
ExpressionDescriptionCron Equivalent
*-*-* 00:00:00Every day at midnight0 0 * * *
*-*-* *:00:00Every hour on the hour0 * * * *
*-*-* *:*:00Every minute* * * * *
*-*-01 00:00:001st of every month at midnight0 0 1 * *
Mon *-*-* 08:00:00Every Monday at 8:00 AM0 8 * * 1
Mon..Fri *-*-* 18:00:00Weekdays at 6:00 PM0 18 * * 1-5
*-*-* 06,18:00:00Every day at 6:00 AM and 6:00 PM0 6,18 * * *
*-*-* 09:00:00/2hEvery 2 hours starting at 9:00 AM—

systemd also provides convenient presets:

PresetEquivalent Expression
minutely*-*-* *:*:00
hourly*-*-* *:00:00
daily*-*-* 00:00:00
weeklyMon *-*-* 00:00:00
monthly*-*-01 00:00:00
yearly*-01-01 00:00:00

Use systemd-analyze calendar to verify whether an expression is correct and preview upcoming trigger times:

Verify a time expression and view the next trigger time
systemd-analyze calendar "Mon..Fri *-*-* 09:00:00"
Verify an expression that triggers every 15 minutes
systemd-analyze calendar "*-*-* *:00/15:00"

Creating a Scheduled Task: Complete Example

Section titled “Creating a Scheduled Task: Complete Example”

The following example demonstrates how to create a timer + service pair using a database backup task.

Create the backup script
sudo vim /opt/scripts/db-backup.sh

Enter the following content:

/opt/scripts/db-backup.sh
#!/bin/bash
BACKUP_DIR="/backup/database"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
mkdir -p "$BACKUP_DIR"
mysqldump --all-databases | gzip > "$BACKUP_DIR/all-db-$TIMESTAMP.sql.gz"
# Delete backups older than 7 days
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +7 -delete
echo "Database backup completed: all-db-$TIMESTAMP.sql.gz"
Grant execute permission to the script
sudo chmod +x /opt/scripts/db-backup.sh
Create the backup service unit file
sudo vim /etc/systemd/system/db-backup.service
/etc/systemd/system/db-backup.service
[Unit]
Description=Database Backup Service
After=mariadb.service
Wants=mariadb.service
[Service]
Type=oneshot
ExecStart=/opt/scripts/db-backup.sh
User=root
StandardOutput=journal
StandardError=journal
# Timeout setting (large database backups may take a long time)
TimeoutStartSec=3600
# Security hardening
PrivateTmp=true
ProtectHome=true

Note: Services triggered by timers typically do not need an [Install] section, since they are started by the timer rather than directly enabled.

Create the backup timer unit file
sudo vim /etc/systemd/system/db-backup.timer
/etc/systemd/system/db-backup.timer
[Unit]
Description=Run Database Backup Daily at 2:00 AM
[Timer]
OnCalendar=*-*-* 02:00:00
RandomizedDelaySec=300
Persistent=true
[Install]
WantedBy=timers.target

Configuration details:

DirectiveDescription
OnCalendar=*-*-* 02:00:00Trigger every day at 2:00 AM
RandomizedDelaySec=300Random delay of 0-300 seconds, to avoid multiple servers executing simultaneously
Persistent=trueIf the execution time was missed (e.g., server was off), run immediately at next startup
Reload the systemd configuration
sudo systemctl daemon-reload
Enable and start the timer
sudo systemctl enable --now db-backup.timer
View the timer status and next trigger time
sudo systemctl status db-backup.timer
Manually trigger the backup service for testing
sudo systemctl start db-backup.service
View the backup service execution logs
sudo journalctl -u db-backup.service -n 20

In addition to calendar-based timers, monotonic timers are suitable for “execute at regular intervals” scenarios:

/etc/systemd/system/health-check.timer - First run 1 minute after boot, then every 5 minutes
[Unit]
Description=Run Health Check Every 5 Minutes
[Timer]
OnBootSec=1min
OnUnitActiveSec=5min
[Install]
WantedBy=timers.target
/etc/systemd/system/health-check.service
[Unit]
Description=System Health Check
[Service]
Type=oneshot
ExecStart=/opt/scripts/health-check.sh
List all active timers and their next trigger times
systemctl list-timers
List all timers (including disabled ones)
systemctl list-timers --all
Stop a timer (does not remove the enable)
sudo systemctl stop db-backup.timer
Disable and stop a timer
sudo systemctl disable --now db-backup.timer

If you have an existing crontab task:

30 3 * * * /opt/scripts/cleanup.sh

The corresponding systemd timer configuration is:

/etc/systemd/system/cleanup.service
[Unit]
Description=Daily Cleanup Task
[Service]
Type=oneshot
ExecStart=/opt/scripts/cleanup.sh
/etc/systemd/system/cleanup.timer
[Unit]
Description=Run Cleanup Daily at 3:30 AM
[Timer]
OnCalendar=*-*-* 03:30:00
Persistent=true
[Install]
WantedBy=timers.target
Enable the migrated timer
sudo systemctl daemon-reload && sudo systemctl enable --now cleanup.timer

After confirming the timer is working correctly, remove the corresponding crontab entry:

Edit crontab to remove the old task
sudo crontab -e
View detailed timer status
sudo systemctl status db-backup.timer
View the most recent execution result of the timer's associated service
sudo systemctl status db-backup.service
View all properties of the timer
systemctl show db-backup.timer
Verify OnCalendar expression syntax
systemd-analyze calendar "*-*-* 02:00:00"
View the complete logs for both the timer and service
sudo journalctl -u db-backup.timer -u db-backup.service --since today