Systemd Timers vs Cron: Modern Linux Task Automation
For decades, cron has been the default standard for scheduling background jobs in Unix-like operating systems. However, modern Linux systems powered by systemd offer a far superior, enterprise-grade alternative: Systemd Timers.
Why Migrate from Cron to Systemd Timers?
| Feature | cron | systemd Timers |
|---|---|---|
| Logging | Silent or requires custom file redirection (>> /var/log/...) | Centralized in journalctl with stdout/stderr capture |
| Execution Controls | Runs blindly regardless of network/disk state | Can depend on targets (e.g., network-online.target) |
| Resource Limits | Unrestricted by default | Full cgroups support (RAM, CPU limits) |
| Missed Executions | Skipped entirely if system is powered off | A persistent calendar timer runs once after boot if at least one scheduled time was missed |
| Execution Visibility | Difficult to see next execution time | Clean inspection with systemctl list-timers |
Architecture: The Service & Timer Pair
A systemd timer requires two paired configuration files located in /etc/systemd/system/ (for system-wide tasks) or ~/.config/systemd/user/ (for unprivileged user tasks):
.servicefile: Defines what executable or script to run..timerfile: Defines when and how often the paired service is triggered.
Step 1: Create the Working Script
Create the Working Script
ScriptingCreate a configuration-backup script under /usr/local/bin/homelab-backup.sh. It skips example paths that are absent and fails clearly if none exist. This archives configuration only; use an application-aware backup method for databases and other live state.
sudo nano /usr/local/bin/homelab-backup.sh❯ View Expected Console Output
#!/usr/bin/env bashset -euo pipefailumask 077
BACKUP_DIR="/var/backups/homelab"DATE="$(date +'%Y-%m-%d_%H%M%S')"mkdir -p "$BACKUP_DIR"
EXPECTED_PATHS=(etc/caddy etc/docker)SOURCES=()for path in "${EXPECTED_PATHS[@]}"; do [[ -e "/$path" ]] && SOURCES+=("$path")doneif ((${#SOURCES[@]} == 0)); then echo "No configured paths exist; update EXPECTED_PATHS." >&2 exit 1fi
echo "[$(date)] Starting configuration backup..."tar -czf "$BACKUP_DIR/etc-backup-$DATE.tar.gz" -C / -- "${SOURCES[@]}"
# Prune archives older than 14 days.find "$BACKUP_DIR" -type f -name "*.tar.gz" -mtime +14 -deleteecho "[$(date)] Backup completed successfully."Ensure the script has executable permissions:
sudo chmod +x /usr/local/bin/homelab-backup.shStep 2: Define the Systemd Service Unit
Define the Systemd Service Unit
Systemd UnitCreate /etc/systemd/system/homelab-backup.service. We use Type=oneshot because batch backup tasks run to completion and exit immediately. We also enforce memory and CPU limits:
[Unit]Description=Automated Homelab Backup RoutineWants=network-online.targetAfter=network-online.target
[Service]Type=oneshotExecStart=/usr/local/bin/homelab-backup.shUser=rootStandardOutput=journalStandardError=journalMemoryMax=512MCPUQuota=50%Step 3: Define the Systemd Timer Unit
Define the Systemd Timer Unit
SchedulingCreate /etc/systemd/system/homelab-backup.timer. For this calendar timer, Persistent=true triggers one catch-up run after boot if at least one scheduled activation was missed while the system was off. It does not replay every missed interval.
[Unit]Description=Trigger Homelab Backup Routine Daily
[Timer]# Run every night at 03:30 AMOnCalendar=*-*-* 03:30:00# Randomized delay prevents I/O contention if multiple jobs fire togetherRandomizedDelaySec=15m# Catch up immediately if machine was asleep/offlinePersistent=true
[Install]WantedBy=timers.targetStep 4: Understanding OnCalendar Schedule Expressions
Verify OnCalendar Schedule Expressions
ValidationUse the built-in systemd-analyze calendar utility to validate your schedule expressions and check when the next execution windows occur:
systemd-analyze calendar "Mon *-*-* 04:00:00"❯ View Expected Console Output
Normalized form: Mon --* 04:00:00
Next elapse: the next Monday at 04:00 in the host’s local time zone
(in UTC): the equivalent UTC time
From now: varies with the time and date when the command is run
Quick Reference: Cron vs Systemd Timers
| Interval | cron Equivalent | systemd Timer OnCalendar Expression |
|---|---|---|
| Every hour | 0 * * * * | hourly or *-*-* *:00:00 |
| Daily at midnight | 0 0 * * * | daily or *-*-* 00:00:00 |
| Every 15 minutes | */15 * * * * | *:0/15 |
| Every Monday at 4 AM | 0 4 * * 1 | Mon *-*-* 04:00:00 |
| Monthly on 1st at 2 AM | 0 2 1 * * | *-*-01 02:00:00 |
Step 5: Enable, Start & Verify the Timer
Enable, Start & Verify the Timer
ActivationReload the systemd daemon to pick up the new unit files, then enable and start the timer (not the service directly):
sudo systemctl daemon-reloadsudo systemctl enable --now homelab-backup.timersystemctl list-timers --all homelab-backup.timer❯ View Expected Console Output
NEXT LEFT LAST PASSED UNIT ACTIVATES
The next scheduled activation is calculated from the timer’s calendar and randomized delay; LAST is n/a before its first run.
Step 6: Testing Execution & Journal Auditing
Testing Execution & Journal Auditing
MonitoringTrigger an on-demand manual execution of the service and inspect the real-time journal logs:
sudo systemctl start homelab-backup.servicesudo journalctl -u homelab-backup.service -n 50 --no-pager❯ View Expected Console Output
— Journal begins at Fri 2026-10-02 00:00:00 CEST, ends at Fri 2026-10-02 13:00:00 CEST. —
Oct 02 13:05:00 server systemd[1]: Starting Automated Homelab Backup Routine…
Oct 02 13:05:01 server homelab-backup.sh[12450]: [Fri Oct 2 13:05:01 CEST 2026] Starting backup routine…
Oct 02 13:05:03 server homelab-backup.sh[12450]: [Fri Oct 2 13:05:03 CEST 2026] Backup completed successfully.
Oct 02 13:05:03 server systemd[1]: homelab-backup.service: Deactivated successfully.
Oct 02 13:05:03 server systemd[1]: Finished Automated Homelab Backup Routine.