Automation and Task Ranking
Automation tools are not interchangeable. The right tool depends on reliability requirements, observability needs, and the kind of trigger you want (time, event, or process supervision). This page ranks common Linux automation approaches using operational criteria, then gives you a selection guide.
- For modern VPS hosts: prefer systemd timers for scheduled work.
- For file-change triggers: prefer systemd.path.
- Use cron when portability matters or when systemd is not available.
- Use supervisors for long-running processes (not scheduled batch jobs).
Ranking rubric (what actually matters)
This rubric is based on production behavior.
| Criterion | Why it matters |
|---|---|
| Logging and auditability | you need a timeline during incidents |
| Missed-run handling | servers reboot; jobs should not vanish |
| Dependency and ordering | backups should wait for mounts/services |
| Concurrency control | overlapping jobs cause corruption and load spikes |
| Security controls | least privilege, resource limits, sandboxing |
| Portability | some distros/containers lack systemd |
| Debuggability | fast root-cause when jobs fail |
Tool ranking (practical default)
This ranking is a "default order" for VPS operations. It is not a universal truth.
| Tool | Trigger type | Best at | Why it ranks well | When it loses |
|---|---|---|---|---|
| systemd timer | time | scheduled jobs | logs, missed-run support, dependencies | non-systemd environments |
| systemd.path | event | file triggers | stable unit model, no custom daemons | needs systemd |
| cron | time | portable schedules | ubiquitous, simple | weak logging, missed runs |
| at | one-time | delayed one-off | simple deferral | not for recurring workloads |
| anacron | time | catch-up | runs jobs that were missed | limited scheduling model |
| supervisord | process | keep running | restarts, monitors long-lived | not a scheduler |
Quick selection guide
| Use case | Recommended tool | Notes |
|---|---|---|
| Daily backups | systemd timer | add dependencies and logs |
| Rotate/prune backups | systemd timer or cron | add locks + logs |
| "Run when a file appears" | systemd.path | build reliable pipelines |
| "Run once later" | at | good for maintenance window tasks |
| Keep a worker running | supervisord | or native systemd service |
Cron improvements (what systemd fixes)
Cron is not "bad". It is just minimal. systemd timers improve on cron by default:
- captured stdout/stderr in journald
- missed run replay (
Persistent=true) - dependencies (
After=network-online.target) - explicit user and environment
- resource limits
Example: cron vs timer for the same job
Cron
15 2 * * * /usr/local/bin/wp-backup-run >>/var/log/wp-backup.log 2>&1
systemd timer
[Unit]
Description=WordPress backup
[Service]
Type=oneshot
ExecStart=/usr/local/bin/wp-backup-run
[Unit]
Description=Run WordPress backup daily
[Timer]
OnCalendar=*-*-* 02:15:00
Persistent=true
[Install]
WantedBy=timers.target
sudo systemctl daemon-reload
sudo systemctl enable --now wp-backup.timer
systemctl list-timers --all | rg -n 'wp-backup'
Notes by environment
- Modern VPS
- Minimal/Container
- Use systemd timers for scheduled tasks.
- Use journald logs for post-mortem debugging.
- Prefer unit dependencies over sleep loops.
- systemd may not be present.
- use cron for scheduling.
- ensure logs are redirected.
- ensure PATH and environment are explicit.
systemd.path vs inotify (file triggers)
If your automation is triggered by files appearing or changing, you have two common approaches:
- inotify-based scripts (
inotifywait) that run continuously - systemd.path units that trigger a service
systemd.path is often easier to operate because it behaves like other systemd units.
Example: run a service when a file appears.
[Unit]
Description=Process incoming files
[Service]
Type=oneshot
ExecStart=/usr/local/bin/process-incoming
[Unit]
Description=Watch incoming directory
[Path]
PathExistsGlob=/srv/incoming/*
[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now process-incoming.path
systemctl status process-incoming.path --no-pager --full
Notes:
PathExistsGlobtriggers when any file matches.- systemd.path is not a replacement for all real-time watchers, but it is stable for many ops pipelines.
Overlap control and job safety
Most automation incidents happen because two copies of the same job run at once. Pick one overlap strategy.
Lock files (cron or timers)
flock -n /var/lock/myjob.lock /usr/local/bin/myjob
Queueing (one at a time)
If tasks can queue, use a queue tool.
ts(task spooler) for sequential job queues- a simple "run next" wrapper around a directory queue
Example with task spooler:
ts /usr/local/bin/backup-run
ts /usr/local/bin/prune-backups
ts
systemd service-level constraints
systemd provides guardrails that cron does not:
RuntimeMaxSec=to prevent runaway jobsStartLimitIntervalSec=andStartLimitBurst=to prevent crash loopsNice=and resource limits
[Service]
Type=oneshot
ExecStart=/usr/local/bin/myjob
RuntimeMaxSec=2h
Nice=10
One-shot vs long-running automation
Do not schedule a daemon with cron. Do not run a batch job under a process supervisor.
| Workload type | Correct tool class |
|---|---|
| Scheduled batch (backup, cleanup) | cron or systemd timer |
| Triggered batch (file appears) | systemd.path or watcher |
| Long-running worker | systemd service / supervisord |
Anti-patterns to avoid
- Running backups from a WordPress plugin UI instead of a scheduler.
- Using
sleepin cron entries to add randomness (use timers withRandomizedDelaySec=). - Running destructive commands without a dry-run step.
- Hiding failures by redirecting to
/dev/null. - Scheduling jobs without locking (overlap is inevitable).
Practice lab
This lab installs a simple timer that writes a heartbeat log.
- Create a script.
sudo install -m 755 /dev/stdin /usr/local/bin/heartbeat <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
echo "[$(date -Is)] heartbeat" >> /var/log/heartbeat.log
EOF
- Create a service and timer.
[Unit]
Description=Write heartbeat log
[Service]
Type=oneshot
ExecStart=/usr/local/bin/heartbeat
[Unit]
Description=Run heartbeat every minute
[Timer]
OnUnitActiveSec=60s
OnBootSec=30s
Persistent=true
[Install]
WantedBy=timers.target
sudo systemctl daemon-reload
sudo systemctl enable --now heartbeat.timer
systemctl list-timers --all | rg -n 'heartbeat'
- Check logs.
sudo tail -n 20 /var/log/heartbeat.log
journalctl -u heartbeat.service --since '10 minutes ago' --no-pager
Next steps
- Cron fundamentals:
opt/docker-data/apps/docusaurus/site/docs/server/linux-server/11-automation-task-execution/cron.mdx - Cron vs systemd:
opt/docker-data/apps/docusaurus/site/docs/server/linux-server/11-automation-task-execution/cron-vs-systemd.mdx - Output and logs:
opt/docker-data/apps/docusaurus/site/docs/server/linux-server/11-automation-task-execution/redirecting-output-and-logs.mdx