Comparison · vs Jobber

RunWisp vs Jobber: same itch, different reach.

Both replace cron with a Go binary. Jobber stops at scheduled jobs and hasn't had a release since 2020; RunWisp adds services, a web dashboard, and notifications that don't need sendmail.

Verdict

Should you switch?

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

Switch to RunWisp if

  • You'd rather run something maintained: Jobber's last release was v1.4.4 in 2020.
  • 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 script. Jobber's program sink does that; RunWisp's closest is 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
Actively maintained ✗last release 2020 ✓
Run history per firing ~jobber log, last 100 in memory ✓SQLite rows + Web UI
Captures stdout / stderr ~via result sinks ✓every run, in the UI
Retries with backoff ~skips next 1/2/4/8 firings ✓constant / linear / exp
Failure notifications ~email, file, or your program ✓Slack / Telegram / ntfy / SMTP / sendmail
Pluggable result sinks ✓program, file, socket ~webhook
Coalesced alerts (no notify storms) ✗ ✓
Overlap policy ✗ ✓skip / queue / kill
Catch-up after downtime ✗ ✓catch_up = 0 / 1 / N
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
Few moving parts ~3 small Go binaries ✓one binary
macOS / WSL / Docker ~Linux + macOS, Docker image deprecated ✓
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.
  • Result sinks: pipe every run's stdout, stderr and exit status to a file, a socket, or your own program.
  • Simple mental model: one YAML file at ~/.jobber, three error policies, done.
  • Frozen and small: it does one thing and hasn't changed since 2020.

What RunWisp adds

  • Maintained: active releases, signed builds, a changelog you can read.
  • 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.
  • Slack, Telegram, Discord, ntfy, email and webhook notifications without a local MTA, with flapping alerts coalesced.
  • Retries re-run the same firing; on_overlap and catch_up are config fields, not your problem.
  • Services (always-on processes) and tasks (scheduled) in one daemon.
  • DST fall-back fires once, spring-forward fires once, shifted forward by the gap.
  • Jobber's six-field time strings carry over unchanged.
The simple case

The simple case

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

.jobber (YAML)
# ~/.jobber
version: 1.4
jobs:
  nightly-backup:
    cmd: /usr/local/bin/backup.sh
    time: '0 0 3 * * *'
    onError: Continue
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. Program sinks (your scripts) on the left; named fields and built-in notifications on the right.

.jobber (production)
# ~/.jobber, hardened for production
version: 1.4
jobs:
  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 No equivalent. Jobber stops scheduling the job after one error; RunWisp keeps firing it.
onError: Backoff retry_attempts = N + retry_backoff = "exponential" (retries the same firing)
onError: Continue Default behaviour; failures are logged, next firing proceeds.
notifyOnError: [{type: program}] notify = ["slack-ops"], or a webhook notifier to reach your own script
jobber log Web UI run list / runwisp tui
jobber reload runwisp reload (validates first, no restart)
No overlap handling (a new goroutine per firing) 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 retry_backoff

Jobber's Backoff policy doesn't retry anything. After each error it skips the next 1, 2, 4, 8 scheduled firings, then stops scheduling the job. RunWisp's retry_attempts + retry_backoff re-runs the same firing after retry_delay. onError: Stop has no RunWisp equivalent: a failed task keeps its schedule.

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

Is Jobber still maintained?
Barely. The last release is v1.4.4 from June 2020, the last commit was in 2021, the README says it isn't actively maintained, and its Docker Hub image is marked deprecated. It still runs, and a small stable tool can be a fine choice. But bugs on newer systems, such as macOS, aren't getting fixed.
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, then jobber reload.
Jobber runs per user. Can RunWisp do that?
RunWisp is system-wide by design. You can run several instances under different users, each with its own config file and data directory, 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.
How do Jobber's error policies map to RunWisp?
Continue is the default in both: a failure is recorded and the next firing proceeds. Backoff skips the next 1, 2, 4, 8 firings and then gives up; the closest RunWisp equivalent is retry_attempts + retry_backoff, which re-runs the current firing instead. Stop (stop scheduling the job after one error) has no RunWisp equivalent.
Does RunWisp need sendmail for notifications?
No. Slack, Telegram, Discord, ntfy, generic webhooks, or SMTP straight to Gmail/SES/Postmark all work without an MTA. If the box already sends mail, type = "sendmail" pipes a plain-text message into whichever sendmail-compatible binary is installed. Jobber can hand every result to your own program; RunWisp has no run-a-script notifier, so use the webhook driver.
Is RunWisp heavier than Jobber?
Yes. RunWisp embeds SQLite for run history and an HTTP server for the dashboard, and an idle daemon with one task sits around 30 MB of RAM. Jobber is lighter. The trade 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, with the same TOML and web UI on Linux, macOS, WSL, and Docker. Jobber targets Linux and macOS, and its Docker image is deprecated.
Get started
GPL 3.0 · no accounts · free to self-host

Outgrown ~/.jobber? One maintained 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