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.
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 -epicks 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/skipinstead 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.
# /etc/cron.d/nightly-backup
0 3 * * * root /usr/local/bin/backup.sh # 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.
# /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, 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
Six-field cron syntax isn't supported
RunWisp parses 5-field cron plus aliases (@hourly, @daily, @every 30s). A seconds field (common in Java schedulers) won't load. For sub-minute work use @every 30s.
No live reload; validate first
Edits to runwisp.toml need a daemon restart. Run runwisp validate --config <path> first; a parse error fails the boot before the daemon opens its port.
catch_up defaults to latest; set skip for probes
A health probe wants fresh state, not a stale catch-up the moment the daemon comes back. Set catch_up = "skip" for probes and metrics scrapes.
Tasks run as the daemon's user
cron lines often run as root because the system crontab does. RunWisp tasks run as the daemon's user. Wrap the run command with sudo -u if a task genuinely needs a different uid.
FAQ
Yes. RunWisp doesn't touch /etc/crontab, /var/spool/cron, or anyone's user crontab. Move one task at a time; comment the cron line, restart the daemon, watch it land in the Web UI.
Not today. Crontab entries are short enough that hand-translating them is fast: schedule into cron =, command into run =, pick a name, decide on_overlap and retry_attempts.
Run it as whoever owns the work. Every task runs as the daemon's user; wrap with sudo -u or runuser for per-task identity, or run multiple daemons under different users.
Cron expressions evaluate in [scheduler] timezone or per-task timezone =. Fall-back fires once (the suppressed firing is still in run history); spring-forward fires once at the next valid minute. UTC is unaffected.
Yes. One Go binary, no runtime. Same TOML, same Web UI on Linux, macOS, WSL, and Docker. cron's macOS / WSL story is patchy; this one isn't.
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.