Comparison · vs Jobber

RunWisp vs Jobber: same itch, different reach.

Both replace cron with a Go binary. One stops at scheduled jobs; the other adds services, a web dashboard, and notifications that don't require sendmail.

Verdict

Should you switch?

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

Switch to RunWisp if

  • You want run history you can browse in a web UI, not just grep in syslog.
  • Notifications should hit Slack or Telegram, not depend on a local sendmail.
  • You also supervise long-running services on the same box.
  • Overlap policies and catch-up after downtime matter.
  • One daemon per system beats one daemon per user.
  • You need the same binary on macOS, WSL, and Docker.

Stay on Jobber if

  • Per-user isolation is a hard requirement (multi-tenant box, each user owns their jobs).
  • You want the lightest possible footprint: no embedded SQLite, no web server.
  • Every job result should pipe into your own notifyProgram script — RunWisp's notifiers are six fixed drivers plus a webhook.
  • One YAML file at ~/.jobber and three error policies is all the model you want.
Head to head

Yes / no, at a glance

Yes / no — and both columns win on something.

Capability Jobber RunWisp
Run history per firing ~jobber log, syslog ✓SQLite rows + Web UI
Captures stdout / stderr ✗ ✓
Retries with backoff ~Backoff on error only ✓constant / linear / exp
Failure notifications ~sendmail or program ✓Slack / Telegram / SMTP / sendmail
Coalesced alerts (no notify storms) ✗ ✓
Overlap policy ✗ ✓skip / queue / kill
Catch-up after downtime ✗ ✓latest / all / skip
DST handling ✗ ✓deduped / shifted
Web UI / dashboard ✗ ✓
TUI over SSH ✗ ✓
Long-running services ✗ ✓[services.*]
Per-user daemon isolation ✓one daemon per user ✗system-wide
Six-field cron (seconds) ✓ ✓optional seconds field
Single static Go binary ✓ ✓
macOS / WSL / Docker ~Linux + macOS ✓
Trade-offs

Pros & cons

What Jobber still does better

  • Per-user daemon model: each Unix user owns their jobs with no shared config or shared daemon.
  • Lighter footprint: no embedded SQLite, no web server, no TUI. Just runs jobs.
  • Simple mental model: one YAML file at ~/.jobber, three error policies, done.
  • Longer track record in the cron-replacement niche.

What RunWisp adds

  • Every firing is a row with status, duration, exit code, and captured stdout/stderr.
  • Web dashboard and TUI for browsing run history without SSH + grep.
  • Six-field cron with an optional seconds field; Jobber's leading column carries over unchanged.
  • Slack, Telegram, Discord, email, and webhook notifications without requiring a local MTA.
  • Overlap policies (skip / queue / kill) are a config field, not your problem.
  • catch_up = 1 replays missed firings after a reboot.
  • Services (always-on processes) and tasks (scheduled) in one daemon.
  • DST fall-back fires once (deduped), spring-forward fires once, shifted forward by the gap.
  • One binary on Linux, macOS, WSL, and Docker. Same TOML everywhere.
The simple case

The simple case

Nightly backup at 03:00. Both are a few lines in a config file.

.jobber (YAML)
# ~/.jobber
[jobs]
- name: nightly-backup
  cmd: /usr/local/bin/backup.sh
  time: '0 0 3 * * *'
  onError: Stop
runwisp.toml
# runwisp.toml
[tasks.nightly-backup]
cron = "0 3 * * *"
run  = "/usr/local/bin/backup.sh"
Production-grade

The version that runs in prod

Same backup, hardened. notifyProgram scripts on the left; named fields and built-in notifications on the right.

.jobber (production)
# ~/.jobber, hardened for production
[jobs]
- name: nightly-backup
  cmd: /usr/local/bin/backup.sh
  time: '0 0 3 * * *'
  onError: Backoff
  notifyOnError:
    - type: program
      path: /usr/local/bin/notify-slack.sh
  notifyOnFailure:
    - type: program
      path: /usr/local/bin/page-ops.sh
runwisp.toml (production)
# runwisp.toml, hardened for production
[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            = ["slack-ops"]
run               = "/usr/local/bin/backup.sh"
Migration

Migration cheat sheet

Jobber concept on the left, RunWisp equivalent on the right.

Jobber RunWisp
~/.jobber (per-user YAML) runwisp.toml (system-wide TOML)
time: '0 0 3 * * *' (6 fields) cron = "0 0 3 * * *" (6-field accepted) or drop the seconds column
cmd: run =
onError: Stop retry_attempts = 0 (default)
onError: Backoff retry_attempts = N + retry_backoff = "exponential"
onError: Continue Default behaviour; failures are logged, next firing proceeds.
notifyOnError: [{type: program}] notify = ["slack-ops"]
jobber log Web UI run list / runwisp tui
jobber reload runwisp reload (validates first, no restart)
No overlap handling on_overlap = "skip"
No catch-up after reboot catch_up = 1 (default)
Gotchas

Gotchas

Six-field cron carries over directly

Jobber's time field has a leading seconds column: 0 0 3 * * *. RunWisp now accepts the same six-field form, so the expression copies over unchanged; the five-field form (0 3 * * *) and @every 30s work too. Quartz operators (L, W, #) are not parsed.

No per-user daemon model

Jobber runs one daemon per Unix user, each reading ~/.jobber. RunWisp is system-wide. If per-user isolation matters (shared hosting, multi-tenant boxes), you lose that boundary.

Jobber's onError: Backoff is not the same as retry_backoff

Jobber's Backoff error policy delays the next scheduled firing, not the current one. RunWisp's retry_attempts + retry_backoff retries the same firing immediately with increasing delays. They solve different problems.

Tasks run as the daemon's user unless you say otherwise

Jobber jobs run as the user whose ~/.jobber they live in. RunWisp tasks run as the daemon's user by default; when the daemon runs as root, set user = "deploy" per task to run as another uid.

FAQ

FAQ

Can RunWisp run alongside Jobber while I migrate?
Yes. They don't share config files, sockets, or state. Move one job at a time: add the task to runwisp.toml, remove it from ~/.jobber, restart both daemons.
Jobber runs per user. Can RunWisp do that?
RunWisp is system-wide by design. You can run multiple instances under different users with separate config files and data directories, but it's not the default workflow. If per-user isolation is a hard requirement, Jobber's model is simpler.
Does RunWisp support Jobber's six-field cron syntax?
Yes. RunWisp accepts an optional leading seconds field, so a Jobber time like 0 0 3 * * * copies over as cron = "0 0 3 * * *". Five-field cron and aliases like @hourly, @daily, @every 30s also work. You can drop the seconds column if you prefer, but you no longer have to.
How do Jobber's error policies map to RunWisp?
Stop = retry_attempts 0 (default). Backoff delays the next scheduled firing; the closest RunWisp equivalent is retry_attempts + retry_backoff, but note that RunWisp retries the current firing, not the next one. Continue = the default; failures are logged and the next cron firing proceeds normally.
Does RunWisp need sendmail for notifications?
No MTA required: Slack, Telegram, Discord, generic webhooks, or SMTP straight to Gmail/SES/Postmark. But if the box already sends mail, sendmail is one of the drivers — type = "sendmail" pipes a plain-text message into whichever sendmail-compatible binary is installed (Postfix, exim, msmtp, ssmtp), with no host, port, TLS mode, or password to configure. That is the closest thing to a crontab MAILTO or Jobber's sendmail path, and it is a choice rather than a dependency. Jobber also shells out to a custom notifyProgram script; RunWisp has no arbitrary-program notifier — use the webhook driver.
Is RunWisp heavier than Jobber?
Yes. RunWisp embeds SQLite for run history and an HTTP server for the web dashboard. Idle RAM is around 25 MB vs Jobber's lighter footprint. The trade-off is queryable history and a UI without extra tooling.
Can RunWisp supervise long-running services too?
Yes. [services.*] blocks define always-on processes with restart backoff, graceful stop, and replicas. Jobber is scheduled jobs only; pair it with supervisord or systemd if you also need services.
Does the same RunWisp binary work on macOS and WSL?
Yes. One Go binary, no runtime deps. Same TOML config, same web UI on Linux, macOS, WSL, and Docker. Jobber supports Linux and macOS but not WSL or Docker out of the box.
Get started
GPL 3.0 · no accounts · free to self-host

Outgrown ~/.jobber? One binary, full history.

Install RunWisp, translate your YAML jobs to TOML stanzas, and watch every firing land in the web dashboard.

Install in 30 seconds
curl -fsSL https://get.runwisp.com | sh