Linux Automation: Crontab Syntax, Systemd Timers & Concurrency Runbook
The battle-tested guide to scheduled task automation in Linux: Master 5-field crontab rules, bypass silent environment failures, prevent overlapping jobs with flock, and migrate to robust Systemd Timers.
1. Crontab Syntax & The 4 Silent Killers
# Edit the current user crontab
crontab -e
# Production Template:
# [Min] [Hour] [DoM] [Month] [DoW] [Absolute Path to Binary] [Arguments] >> [Logfile] 2>&1
# Example: Run database backup every night at 02:30 UTC with full logging
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
MAILTO=sysadmin@example.com
30 2 * * * /usr/local/bin/backup-db.sh >> /var/log/backup-db.log 2>&1Always specify SHELL, explicit PATH, and capture both stdout and stderr (2>&1) into a timestamped or rotatable log file.
# 2) % must be escaped as \% inside the command field:
0 3 * * * /usr/bin/tar -czf /backup/db-$(date +\%F).tgz /var/lib/db
# 3) Day-of-month AND day-of-week are OR-ed when both are set:
# runs on the 1st of the month AND every Monday (not only Mondays that are the 1st)
0 0 1 * 1 /usr/local/bin/report.sh
# 4) Time zone: pin it to avoid DST surprises (CRON_TZ is supported by cronie; check man 5 crontab on your distro)
CRON_TZ=UTC
30 2 * * * /usr/local/bin/backup-db.shEscape percent signs, remember the OR rule for day fields, and prefer UTC schedules on servers.
| Field | Allowed Values | Special Operators | Copy |
|---|---|---|---|
| Minute | 0 - 59 | * (every), , (list: 15,45), - (range: 1-5), / (step: */10) | |
| Hour | 0 - 23 | 24-hour UTC or server local time format | |
| Day of Month | 1 - 31 | Matches calendar day | |
| Month | 1 - 12 (or JAN-DEC) | Calendar month selector | |
| Day of Week | 0 - 7 (0 or 7 = SUN) | 1=MON, 2=TUE, 3=WED, 4=THU, 5=FRI, 6=SAT |
2. Preventing Overlapping Jobs with flock
# Run every 5 minutes; if previous job is still running, abort immediately without error
*/5 * * * * /usr/bin/flock -n /var/lock/sync-orders.lock /usr/local/bin/sync-orders.sh >> /var/log/orders.log 2>&1
# flock flags:
# -n, --nonblock : Fail immediately with exit code 1 if lock is held
# -x, --exclusive: Acquire exclusive write lock (default)
# -w 30 : Wait up to 30 seconds for lock before giving upflock releases the kernel file descriptor lock automatically even if the script crashes or is terminated by SIGKILL.
3. Architectural Comparison: Cron vs Systemd Timers
| Feature | Cron / Crontab | Systemd Timers | Copy |
|---|---|---|---|
| Logging | Output emailed or silently discarded unless redirected | Built-in centralized logging via journalctl | |
| Missed Runs (Server Offline) | Skipped permanently | Persistent=true triggers missed job on boot | |
| Execution Jitter | Requires custom sleep random hacks | RandomizedDelaySec=5m avoids thundering herd | |
| Service Dependencies | None (job fails if DB/network is down) | Requires=, After=network-online.target | |
| Resource Constraints | Runs with raw user limits | CPUQuota=, MemoryMax= cgroup isolation |
4. Creating a Production Systemd Timer & Service Unit
# /etc/systemd/system/db-backup.service
[Unit]
Description=Daily Database Dump & S3 Sync
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
User=postgres
Group=postgres
ExecStart=/usr/local/bin/backup-database.sh
StandardOutput=journal
StandardError=journal
# Enforce resource isolation
MemoryMax=2G
CPUQuota=80%Type=oneshot ensures systemd waits until the backup finishes before considering the run complete.
# /etc/systemd/system/db-backup.timer
[Unit]
Description=Trigger Daily Database Dump at 03:00 UTC
[Timer]
# Calendar schedule format: DayOfWeek Year-Month-Day Hour:Minute:Second
OnCalendar=*-*-* 03:00:00 UTC
# If server was turned off during scheduled time, run immediately upon startup
Persistent=true
# Delay each run by a random 0-5 minutes to spread load across servers
RandomizedDelaySec=5m
Unit=db-backup.service
[Install]
WantedBy=timers.targetOnCalendar supports expressive time specs like "Mon..Fri *-*-* 08:30:00" or "hourly".
5. Monitoring, Testing & Journal Diagnostics
# Validate the calendar expression and preview the next trigger times
systemd-analyze calendar "*-*-* 03:00:00 UTC" --iterations=3
# Lint the unit files for typos and bad directives
systemd-analyze verify /etc/systemd/system/db-backup.service /etc/systemd/system/db-backup.timer
# Reload daemon and enable timer to survive reboots
sudo systemctl daemon-reload
sudo systemctl enable --now db-backup.timer
# List all active timers, next run time, and remaining countdown
systemctl list-timers --all
# Test running the service manually right now (without waiting for timer)
sudo systemctl start db-backup.service
# Inspect live execution logs for the job
journalctl -u db-backup.service -n 50 --no-pager
# Follow logs in real-time
journalctl -u db-backup.service -fsystemctl list-timers shows LAST, NEXT, and LEFT countdown timestamps for every scheduled timer on the machine.
Related Free Online Tools
Put these techniques into practice immediately with our privacy-first browser utilities:
Translate cryptic 5-field UNIX crontab schedules into plain English and compose schedules visually.
Convert between octal (755, 644) and symbolic permissions for executable automation scripts.
Convert a UTC schedule to local time zones before writing crontab or OnCalendar entries.