Systemd Timers as Cron Alternative: Modern Task Scheduling
Introduction
Systemd timers provide a modern, powerful alternative to traditional cron jobs for scheduling tasks on Linux systems. While cron has served Unix-like systems for decades, systemd timers offer enhanced features including dependency management, resource control, detailed logging, and integration with the systemd ecosystem. Understanding systemd timers is essential for modern system administration and DevOps practices.
This comprehensive guide explores systemd timers from basics to advanced patterns, demonstrating how to migrate from cron and implement sophisticated scheduling scenarios.
Why Choose Systemd Timers Over Cron?
Advantages of Systemd Timers
Better Logging: Integration with journald provides structured, queryable logs Dependency Management: Start tasks only when specific services are available Resource Control: Use cgroups for CPU, memory, and I/O limits Event-Based Triggers: Trigger on boot, service start, or file changes Calendar Expressions: More flexible scheduling syntax Failure Handling: Automatic retry with configurable backoff Environment Management: Better control over execution environment Monitoring: Native status checking and integration with monitoring tools
When to Use Systemd Timers
- Modern Linux distributions (Ubuntu 16.04+, CentOS 7+, Debian 8+)
- Tasks requiring resource limits
- Complex dependency chains
- Need for detailed logging and monitoring
- Integration with other systemd services
Prerequisites
- Linux system with systemd (check:
systemctl --version) - Root or sudo access
- Basic understanding of systemd services
- Familiarity with cron concepts
Systemd Timer Basics
Timer Components
Every systemd timer consists of two files:
- Service Unit (
.service): Defines what to execute - Timer Unit (
.timer): Defines when to execute
File Locations
# System timers
/etc/systemd/system/
# User timers
~/.config/systemd/user/
# System defaults
/lib/systemd/system/
Creating Your First Timer
Example: Daily Backup Timer
Step 1: Create the Service Unit
# /etc/systemd/system/backup.service
[Unit]
Description=Daily Backup Service
Wants=network-online.target
After=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup.sh
User=backup
Group=backup
StandardOutput=journal
StandardError=journal
# Resource limits
CPUQuota=50%
MemoryLimit=512M
Step 2: Create the Timer Unit
# /etc/systemd/system/backup.timer
[Unit]
Description=Daily Backup Timer
Requires=backup.service
[Timer]
# Run daily at 2:00 AM
OnCalendar=daily
OnCalendar=*-*-* 02:00:00
Persistent=true
# Run immediately if missed
Persistent=true
[Install]
WantedBy=timers.target
Step 3: Enable and Start
# Reload systemd
sudo systemctl daemon-reload
# Enable timer (start on boot)
sudo systemctl enable backup.timer
# Start timer now
sudo systemctl start backup.timer
# Check status
sudo systemctl status backup.timer
# List all timers
systemctl list-timers
# View logs
journalctl -u backup.service
Calendar Expression Syntax
Time Specifications
# OnCalendar examples
# Every minute
OnCalendar=*-*-* *:*:00
# Every 5 minutes
OnCalendar=*-*-* *:0/5:00
# Every hour
OnCalendar=hourly
OnCalendar=*-*-* *:00:00
# Every day at midnight
OnCalendar=daily
OnCalendar=*-*-* 00:00:00
# Every day at specific time
OnCalendar=*-*-* 03:30:00
# Every Monday at 9 AM
OnCalendar=Mon *-*-* 09:00:00
# Weekdays at 6 PM
OnCalendar=Mon..Fri *-*-* 18:00:00
# First day of month
OnCalendar=*-*-01 00:00:00
# Every 15 minutes
OnCalendar=*:0/15
# Multiple times
OnCalendar=*-*-* 06:00:00
OnCalendar=*-*-* 18:00:00
# Business hours (9-5, Mon-Fri)
OnCalendar=Mon..Fri 09..17:00:00
Test Calendar Expressions
# Test calendar expression
systemd-analyze calendar "Mon..Fri *-*-* 09:00:00"
# Output shows:
# Original form: Mon..Fri *-*-* 09:00:00
# Normalized form: Mon..Fri *-*-* 09:00:00
# Next elapse: Mon 2024-01-15 09:00:00
# From now: 3 days left
Advanced Timer Configurations
1. System Health Check Timer
# /etc/systemd/system/health-check.service
[Unit]
Description=System Health Check
After=network.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/health-check.sh
User=monitoring
StandardOutput=journal
StandardError=journal
# Timeout after 5 minutes
TimeoutStartSec=300
# Restart on failure
Restart=on-failure
RestartSec=60
# /etc/systemd/system/health-check.timer
[Unit]
Description=Run Health Check Every 5 Minutes
[Timer]
# Every 5 minutes
OnBootSec=5min
OnUnitActiveSec=5min
# Run immediately on boot
OnBootSec=1min
[Install]
WantedBy=timers.target
2. Database Backup with Dependencies
# /etc/systemd/system/db-backup.service
[Unit]
Description=Database Backup
Requires=postgresql.service
After=postgresql.service
[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup-database.sh
# Run as specific user
User=postgres
Group=postgres
# Set environment
Environment="PGDATA=/var/lib/postgresql/data"
EnvironmentFile=/etc/db-backup.conf
# Resource limits
Nice=19
IOSchedulingClass=2
IOSchedulingPriority=7
# Logging
StandardOutput=journal+console
StandardError=journal+console
# /etc/systemd/system/db-backup.timer
[Unit]
Description=Daily Database Backup at 2 AM
Requires=db-backup.service
[Timer]
# Daily at 2 AM
OnCalendar=*-*-* 02:00:00
# Catch up if missed
Persistent=true
# Randomize by up to 30 minutes
RandomizedDelaySec=1800
[Install]
WantedBy=timers.target
3. Log Rotation Timer
# /etc/systemd/system/log-rotation.service
[Unit]
Description=Rotate Application Logs
[Service]
Type=oneshot
ExecStart=/usr/local/bin/rotate-logs.sh
# Run with restricted privileges
User=logger
Group=logger
PrivateTmp=yes
ProtectSystem=strict
ReadWritePaths=/var/log/app
StandardOutput=journal
# /etc/systemd/system/log-rotation.timer
[Unit]
Description=Rotate Logs Daily at Midnight
[Timer]
# Daily at midnight
OnCalendar=daily
# Also run weekly even if not triggered
OnCalendar=weekly
Persistent=true
[Install]
WantedBy=timers.target
4. Cleanup Timer with Multiple Schedules
# /etc/systemd/system/cleanup.service
[Unit]
Description=Cleanup Temporary Files
[Service]
Type=oneshot
ExecStart=/usr/local/bin/cleanup-temp.sh
ExecStart=/usr/local/bin/cleanup-old-logs.sh
ExecStart=/usr/local/bin/cleanup-cache.sh
User=root
StandardOutput=journal
# /etc/systemd/system/cleanup.timer
[Unit]
Description=Regular System Cleanup
[Timer]
# Run daily at 3 AM
OnCalendar=*-*-* 03:00:00
# Also run weekly on Sunday
OnCalendar=Sun *-*-* 01:00:00
# And monthly on first day
OnCalendar=*-*-01 02:00:00
Persistent=true
AccuracySec=1h
[Install]
WantedBy=timers.target
Migrating from Cron to Systemd Timers
Cron to Timer Conversion
| Cron Expression | Systemd Timer Equivalent |
|---|---|
*/5 * * * * | OnUnitActiveSec=5min |
0 2 * * * | OnCalendar=*-*-* 02:00:00 |
0 0 * * 0 | OnCalendar=Sun *-*-* 00:00:00 |
0 0 1 * * | OnCalendar=*-*-01 00:00:00 |
@reboot | OnBootSec=1min |
@daily | OnCalendar=daily |
@hourly | OnCalendar=hourly |
Conversion Script
#!/bin/bash
# cron-to-systemd.sh
# Convert cron job to systemd timer
CRON_SCHEDULE="$1"
SERVICE_NAME="$2"
COMMAND="$3"
# Create service file
cat > "/etc/systemd/system/${SERVICE_NAME}.service" <<EOF
[Unit]
Description=${SERVICE_NAME} Service
[Service]
Type=oneshot
ExecStart=${COMMAND}
StandardOutput=journal
StandardError=journal
EOF
# Create timer file based on cron schedule
# (Simplified conversion)
TIMER_SPEC=""
case "$CRON_SCHEDULE" in
"@hourly")
TIMER_SPEC="OnCalendar=hourly"
;;
"@daily")
TIMER_SPEC="OnCalendar=daily"
;;
"@weekly")
TIMER_SPEC="OnCalendar=weekly"
;;
*)
echo "Manual conversion required for: $CRON_SCHEDULE"
;;
esac
cat > "/etc/systemd/system/${SERVICE_NAME}.timer" <<EOF
[Unit]
Description=${SERVICE_NAME} Timer
[Timer]
${TIMER_SPEC}
Persistent=true
[Install]
WantedBy=timers.target
EOF
systemctl daemon-reload
echo "Created ${SERVICE_NAME}.service and ${SERVICE_NAME}.timer"
Advanced Features
1. Event-Based Triggers
# /etc/systemd/system/process-uploads.service
[Unit]
Description=Process New Uploads
[Service]
Type=oneshot
ExecStart=/usr/local/bin/process-uploads.sh
# /etc/systemd/system/process-uploads.path
[Unit]
Description=Monitor Uploads Directory
[Path]
# Watch for new files
PathChanged=/var/uploads
# Trigger service
Unit=process-uploads.service
[Install]
WantedBy=multi-user.target
2. Resource Management
# /etc/systemd/system/heavy-task.service
[Unit]
Description=Resource-Intensive Task
[Service]
Type=oneshot
ExecStart=/usr/local/bin/heavy-task.sh
# CPU limit (50%)
CPUQuota=50%
# Memory limit
MemoryLimit=1G
MemoryMax=1G
# I/O priority (best effort, low priority)
IOSchedulingClass=2
IOSchedulingPriority=7
# Nice value
Nice=19
# Limit open files
LimitNOFILE=1024
3. Failure Handling and Retries
# /etc/systemd/system/api-sync.service
[Unit]
Description=Sync with External API
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/api-sync.sh
# Retry on failure
Restart=on-failure
RestartSec=60
# Maximum restart attempts
StartLimitBurst=3
StartLimitIntervalSec=300
# Timeout
TimeoutStartSec=180
Managing Systemd Timers
Essential Commands
# List all timers
systemctl list-timers --all
# Show timer details
systemctl status backup.timer
# Show service status
systemctl status backup.service
# View logs
journalctl -u backup.service
journalctl -u backup.service --since "1 hour ago"
journalctl -u backup.service -f # Follow
# Start timer manually
systemctl start backup.timer
# Stop timer
systemctl stop backup.timer
# Enable on boot
systemctl enable backup.timer
# Disable
systemctl disable backup.timer
# Restart timer
systemctl restart backup.timer
# Trigger service immediately
systemctl start backup.service
# Check when timer will run next
systemctl list-timers backup.timer
# Reload configuration
systemctl daemon-reload
Monitoring and Debugging
# Check timer calendar schedule
systemd-analyze calendar "*-*-* 02:00:00"
# Verify timer configuration
systemd-analyze verify /etc/systemd/system/backup.timer
# View timer properties
systemctl show backup.timer
# Check service dependencies
systemctl list-dependencies backup.service
# Debug mode
SYSTEMD_LOG_LEVEL=debug systemctl start backup.service
User Timers
Creating User Timers
# Create user systemd directory
mkdir -p ~/.config/systemd/user
# Create service
cat > ~/.config/systemd/user/my-task.service <<EOF
[Unit]
Description=My Personal Task
[Service]
Type=oneshot
ExecStart=/home/user/bin/my-task.sh
EOF
# Create timer
cat > ~/.config/systemd/user/my-task.timer <<EOF
[Unit]
Description=My Task Timer
[Timer]
OnCalendar=daily
Persistent=true
[Install]
WantedBy=timers.target
EOF
# Reload user daemon
systemctl --user daemon-reload
# Enable and start
systemctl --user enable --now my-task.timer
# List user timers
systemctl --user list-timers
Enable Lingering
# Allow user timers to run when not logged in
sudo loginctl enable-linger $USER
# Check linger status
loginctl show-user $USER | grep Linger
Best Practices
1. Naming Conventions
# Use descriptive names
backup-database.timer
cleanup-logs.timer
check-ssl-certificates.timer
2. Documentation
[Unit]
Description=Clear description of what this timer does
Documentation=https://wiki.company.com/backup-procedure
3. Failure Notifications
# /etc/systemd/system/backup.service
[Service]
ExecStart=/usr/local/bin/backup.sh
OnFailure=failure-notification@%n.service
4. Testing
# Test service before enabling timer
systemctl start backup.service
# Check logs
journalctl -u backup.service -n 50
# Verify exit status
systemctl show -p Result backup.service
5. Security
[Service]
# Run as specific user
User=backup
Group=backup
# Sandboxing
PrivateTmp=yes
ProtectSystem=strict
ProtectHome=yes
ReadWritePaths=/backup
# Capabilities
NoNewPrivileges=yes
Troubleshooting
Common Issues
Timer not running:
# Check if enabled
systemctl is-enabled backup.timer
# Check status
systemctl status backup.timer
# View systemd logs
journalctl -xe
Service fails:
# Check service logs
journalctl -u backup.service -n 100
# Test service manually
systemctl start backup.service
# Check service file syntax
systemd-analyze verify /etc/systemd/system/backup.service
Timer runs at wrong time:
# Verify calendar expression
systemd-analyze calendar "daily"
# Check timer configuration
systemctl cat backup.timer
# List next trigger time
systemctl list-timers backup.timer
Conclusion
Systemd timers provide a modern, feature-rich alternative to cron for scheduling tasks on Linux systems. With better logging, resource management, and integration with the systemd ecosystem, timers offer significant advantages for system administration and automation.
Key takeaways:
- Systemd timers consist of .service and .timer units
- Calendar expressions are more flexible than cron syntax
- Integration with journald provides better logging
- Resource limits and security features are built-in
- Dependencies and ordering are explicitly managed
- User timers enable non-root task scheduling
Start migrating critical cron jobs to systemd timers to benefit from modern features while maintaining reliability. Combine timers with other systemd features like path units and socket activation for even more powerful automation patterns.


