Skip to content
5 min read·Lesson 9 of 10

Scheduling: cron and systemd Timers

Run scripts on a schedule with cron, systemd timers, and the modern alternatives — including how to log and monitor them.

The unattended script is where Bash earns its keep. The Linux ecosystem gives you two main schedulers — cron, the classic, and systemd timers, the modern.

cron Basics

Each user has a crontab:

crontab -e          # edit your crontab
crontab -l          # list
crontab -r          # remove all (careful)

System-wide cron jobs live in /etc/crontab and /etc/cron.d/*. Drop-in directories like /etc/cron.daily/ run every script there at the daily time.

cron Expression

┌───────────── minute (0–59)
│ ┌─────────── hour (0–23)
│ │ ┌───────── day of month (1–31)
│ │ │ ┌─────── month (1–12)
│ │ │ │ ┌───── day of week (0–6, Sunday = 0 or 7)
│ │ │ │ │
* * * * *  command to run
ScheduleMeaning
0 * * * *Top of every hour
*/15 * * * *Every 15 minutes
0 2 * * *02:00 every day
0 9 * * 1-509:00 weekdays
30 4 1 * *04:30 on the 1st
@hourly / @daily / @rebootShorthand keywords

Use a tool like crontab.guru when expressions get tricky.

cron Pitfalls

  • Minimal environment. No PATH like your shell; no HOME beyond a default. Always set PATH at the top of your crontab or use absolute paths.
  • Output goes to email. If mail is configured, cron emails the user with stdout/stderr. If not, it vanishes. Redirect explicitly.
  • No retries. If a job fails, cron does not try again. You will only know if you alert.
  • Overlap. If a job takes longer than its interval, multiple copies run at once. Use flock to prevent.
# Sample crontab with safety
SHELL=/bin/bash
PATH=/usr/local/bin:/usr/bin:/bin
MAILTO=ops@example.com

# nightly backup, exclusive lock, log everything
0 2 * * *  flock -n /var/lock/backup.lock /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1

systemd Timers

Modern Linux distros run systemd. A timer unit triggers a service unit on a schedule.

Service unit: /etc/systemd/system/backup.service

[Unit]
Description=Nightly backup

[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup.sh
User=backup
Nice=10
IOSchedulingClass=idle
StandardOutput=journal
StandardError=journal

Timer unit: /etc/systemd/system/backup.timer

[Unit]
Description=Run backup nightly

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

[Install]
WantedBy=timers.target

Activate

sudo systemctl daemon-reload
sudo systemctl enable --now backup.timer
systemctl list-timers
journalctl -u backup.service -n 100
systemctl start backup.service     # run now manually

Why systemd Timers Are Better

  • Persistent — missed runs (machine off) execute on next boot.
  • Logging — stdout/stderr go to the journal automatically; journalctl -u name reads them.
  • Dependencies — only run after the network is up, after another service, etc.
  • Isolation — sandbox with PrivateTmp, ProtectSystem, NoNewPrivileges.
  • Retries — built-in with Restart, OnFailure handlers.
  • Resource control — CPU, memory, IO classes.

OnCalendar Examples

ExpressionWhen
*-*-* 02:00:00Daily at 02:00
Mon..Fri 09:00Weekdays at 09:00
hourlyTop of every hour
dailyMidnight UTC
weeklyMonday 00:00
*-*-* *:0/15:00Every 15 minutes

Use systemd-analyze calendar "expression" to verify your expression and preview next runs.

Locking

Prevent overlapping runs with flock:

flock -n /var/lock/myjob.lock /usr/local/bin/myjob.sh
# -n: fail immediately if lock is held; safer than waiting forever

Monitoring

Cron and timers are silent on success. The hardest failure mode is "the job stopped running last week and nobody noticed." Defences:

  • Dead-man-switch services like Healthchecks.io or Cronitor — your script POSTs after success; if it doesn't, you get paged.
  • Metrics push — write last-success timestamp to Prometheus, alert if stale.
  • Mail on failure — set MAILTO in cron and configure mail.
  • OnFailure= in systemd to run an alerting service unit.
#!/usr/bin/env bash
set -euo pipefail

trap 'curl -fsS --retry 3 https://hc-ping.com/UUID/fail' ERR
do_actual_work
curl -fsS --retry 3 https://hc-ping.com/UUID

Cloud Equivalents

CloudService
AWSEventBridge Scheduler / Cron rules → Lambda or ECS task
AzureLogic Apps recurring trigger / Azure Automation runbook
GCPCloud Scheduler → Pub/Sub / Cloud Run / Cloud Functions
KubernetesCronJob resource

The same patterns apply: cron expression, idempotent script, logging, monitoring.

Cert Mapping

CertScope
RHCSA / LFCScron, anacron, systemd timers — direct exam objectives
AWS / Azure / GCPManaged scheduler services on each cloud
CKAKubernetes CronJob

The final lesson covers the discipline that catches most shell bugs before they reach production: ShellCheck and a few unforgiving best practices.

Key Takeaways

  • cron is universal; systemd timers are richer and better integrated on modern Linux.
  • cron expressions are five fields: minute hour day month weekday.
  • Always log cron output and monitor for missed runs.
  • systemd timers give you depend-on, retries, and journal logging for free.
  • Use a dead-man-switch service (Healthchecks, Cronitor) for critical jobs.

Test your knowledge

Try exam-style practice questions to reinforce what you've learned.

Practice Questions →