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
| Schedule | Meaning |
|---|---|
0 * * * * | Top of every hour |
*/15 * * * * | Every 15 minutes |
0 2 * * * | 02:00 every day |
0 9 * * 1-5 | 09:00 weekdays |
30 4 1 * * | 04:30 on the 1st |
@hourly / @daily / @reboot | Shorthand keywords |
Use a tool like crontab.guru when expressions get tricky.
cron Pitfalls
- Minimal environment. No
PATHlike your shell; noHOMEbeyond a default. Always setPATHat 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
flockto 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 namereads them. - Dependencies — only run after the network is up, after another service, etc.
- Isolation — sandbox with
PrivateTmp,ProtectSystem,NoNewPrivileges. - Retries — built-in with
Restart,OnFailurehandlers. - Resource control — CPU, memory, IO classes.
OnCalendar Examples
| Expression | When |
|---|---|
*-*-* 02:00:00 | Daily at 02:00 |
Mon..Fri 09:00 | Weekdays at 09:00 |
hourly | Top of every hour |
daily | Midnight UTC |
weekly | Monday 00:00 |
*-*-* *:0/15:00 | Every 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
MAILTOin 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
| Cloud | Service |
|---|---|
| AWS | EventBridge Scheduler / Cron rules → Lambda or ECS task |
| Azure | Logic Apps recurring trigger / Azure Automation runbook |
| GCP | Cloud Scheduler → Pub/Sub / Cloud Run / Cloud Functions |
| Kubernetes | CronJob resource |
The same patterns apply: cron expression, idempotent script, logging, monitoring.
Cert Mapping
| Cert | Scope |
|---|---|
| RHCSA / LFCS | cron, anacron, systemd timers — direct exam objectives |
| AWS / Azure / GCP | Managed scheduler services on each cloud |
| CKA | Kubernetes CronJob |
The final lesson covers the discipline that catches most shell bugs before they reach production: ShellCheck and a few unforgiving best practices.