RunWisp

Comparison · vs cron

RunWisp vs cron: every fire, captured.

A crontab line and the equivalent RunWisp TOML, with the operational behaviour where they diverge: catch-up policy, DST handling, crashed-run detection.

Quick start GitHub

Should you switch?

The 10-second version. Skim, decide, scroll down for the receipts.

Switch to RunWisp if

  • You've ever asked: did the 03:00 backup actually run?
  • Reboots eat firings and you find out hours later.
  • Your DST line silently fires twice or zero times.
  • Failures should ping Slack, not the local MTA.
  • You want retries with backoff per task, not per script.
  • Same host runs macOS, WSL, or a no-systemd container.

Stay on cron if

  • Three lines in /etc/crontab and none ever fail.
  • You need live reload on every crontab -e save.
  • Container is one RUN echo … | crontab - line.
  • Sub-minute firings (cron seconds field) are mandatory.
  • Adding a daemon costs more than the problem.

Yes / no, at a glance

Yes / no, both columns win on something.

Feature cron RunWisp
Run history per firing
SQLite rows
Captures stdout / stderr
Retries with backoff
constant / linear / exp
Failure notifications (Slack, etc.)
Coalesced alerts (no notify storms)
Catch-up after downtime
latest / all / skip
DST fall-back safety
fires twice
deduped
DST spring-forward safety
skipped silently
fires at next valid minute
Overlap policy
you write flock
Crashed-run detection on boot
Web UI / dashboard
TUI over SSH
Live config reload
restart daemon
Pre-installed everywhere
ship a binary
Sub-minute (seconds field)
BSD/cronie no
~ @every 30s
macOS / WSL / Docker reach
~ varies
Per-task user identity
~ sudo wrapper

Pros & cons

What cron still does better

  • Already installed; nothing extra to ship in the base image.
  • crontab -e picks up edits on save; no daemon restart.
  • One RUN echo … | crontab - line fits in any Dockerfile.
  • Per-line User= via the system crontab.
  • 50 years of weird-edge-case forum posts to crib from.

What RunWisp adds

  • Every firing is a row with status, duration, exit code, and captured logs.
  • Retries and backoff live on the task, not in a wrapper script.
  • DST is named behaviour, not an annual silent bug.
  • catch_up = "latest" / all / skip instead of "hope nobody rebooted."
  • Slack / Telegram / email notifiers coalesce flapping into one alert plus a summary.
  • One binary on Linux, macOS, WSL, Docker — no Python, no MTA.
  • Disk-fill failures raise a visible event instead of wedging the next firing.

The simple case

Nightly backup at 03:00. One line, one stanza.

crontab
crontab
# /etc/cron.d/nightly-backup
0 3 * * *  root  /usr/local/bin/backup.sh
runwisp.toml
runwisp.toml
# runwisp.toml
[tasks.nightly-backup]
cron = "0 3 * * *"
run  = "/usr/local/bin/backup.sh"

Production-grade

Same backup, hardened. flock + redirect + || mailx on the left; named fields on the right.

crontab (production)
crontab (production)
# /etc/cron.d/nightly-backup, the version that actually runs in prod
[email protected]
0 3 * * *  root  flock -n /var/run/backup.lock \
    /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1 \
    || mailx -s "[FAIL] nightly-backup on $(hostname)" [email protected] < /var/log/backup.log
runwisp.toml (production)
runwisp.toml (production)
# runwisp.toml, the version that actually runs in prod
[tasks.nightly-backup]
description       = "Snapshot the data volume to off-site storage"
cron              = "0 3 * * *"
on_overlap        = "skip"            # never two backups at once
timeout           = "55m"             # die before the next firing
retry_attempts    = 2
retry_delay       = "30s"
retry_backoff     = "exponential"
keep_runs         = 60                # two months of history
notify_on_failure = ["slack-ops"]
run               = "/usr/local/bin/backup.sh"

Migration cheat sheet

Concrete mappings from cron concepts to runwisp.toml fields.

cron RunWisp
[email protected] notify_on_failure = ["slack-ops"]
flock -n /var/run/x.lock on_overlap = "skip"
|| mailx … on $(hostname) notify_on_failure + coalesce_window
wrapper loop with sleep + counter retry_attempts + retry_backoff
>> /var/log/job.log 2>&1 Captured automatically; download per-run from Web UI.
@reboot Service in [services.*], not a task.
0 3 * * * on a host that reboots catch_up = "latest" (default)
crontab -e (live reload) runwisp validate then restart daemon
30 2 * * * across DST Deduped on fall-back; fires at next valid minute on spring-forward
cron line as User=root Wrap with sudo -u in run

Gotchas

FAQ

More comparisons: RunWisp vs supervisord · RunWisp vs systemd timers · RunWisp vs PM2 .

Move one crontab line. See it run.

Install the binary, paste a five-field cron expression into runwisp.toml, watch the first firing land in the Web UI.