RunWisp vs Cronitor: monitor the job, or run it and monitor it?
Cronitor is a hosted monitor: your jobs report in, and it alerts when they fail, run long or go quiet. RunWisp is a self-hosted runner: it starts the job itself, so the result is recorded without anything reporting in. Different jobs, and they combine well.
Should you switch?
The 10-second version. Skim, decide, scroll down for the receipts.
Use RunWisp if
- Your Cronitor bill grows with every cron job you add.
- Run times, exit codes and job output shouldn't leave your server.
- You want retries, timeouts and overlap rules applied, not only reported.
- The box is air-gapped or behind strict egress rules.
- You'd rather keep schedule and alert rules together in a file in git.
- You also have long-running services to keep alive.
Keep Cronitor if
- You need to hear about a server that died. Only an outside monitor can tell you.
- Jobs run across many hosts and Kubernetes, and you want one dashboard for all of them.
- You use PagerDuty, Opsgenie or SMS on-call, team roles and SSO.
- You also want uptime checks and public status pages from the same vendor.
- Your jobs run in places you can't install a daemon: managed platforms, Sidekiq, Airflow.
Yes / no, at a glance
Yes / no — and both columns win on something.
| Capability | Cronitor | RunWisp |
|---|---|---|
| Schedules and runs the job | ✗cron still schedules | ✓ |
| Exit code + duration per run | ✓via cronitor exec | ✓ |
| Captured output | ✓uploaded to Cronitor | ✓stays on the box |
| Retries / timeouts / overlap policy | ✗reports only | ✓ |
| Failure alerts | ✓11 integrations + SMS | ✓8 channels + inbox |
| Alerts when the whole host dies | ✓ | ✗pair with a heartbeat |
| Duration / “ran too long” alerts | ✓assertions | ~timeout fails the run |
| One dashboard across hosts + Kubernetes | ✓ | ✗one daemon per host |
| Uptime checks + status pages | ✓ | ~curl task, no status page |
| Teams, roles, SSO | ✓Business | ✗one password |
| Self-hosted; data stays on your box | ✗ | ✓local SQLite |
| Works with no outbound internet | ✗ | ✓ |
| Free with no job limit | ~5 monitors free, then $2 each | ✓ |
| Config as code in git | ~API / CLI sync | ✓runwisp.toml |
| Long-running services | ✗ | ✓ |
Pros & cons
What Cronitor does better
- Watches from outside, so a dead server, a stopped crond or a lost network is caught like any other failure.
- One dashboard for jobs on every host, in Kubernetes (a Helm chart watches every CronJob), and in Sidekiq, Airflow or ActiveJob through SDKs.
- Duration assertions, failure tolerance and follow-up alerts, tuned per monitor.
- Eleven integrations, SMS, PagerDuty and Opsgenie, plus team roles and SAML SSO on Business.
- Uptime checks and status pages in the same product.
- Nothing to keep alive on your side: it's someone else's pager at 3am.
What RunWisp adds
- Runs the job instead of watching it. No wrapper, no API key on every host, no step between cron and the report.
timeout,retry_attempts,on_overlapandcatch_upact on the job. A monitor can only tell you they were needed.- Output, run times and exit codes stay in SQLite on the box. Nothing about your jobs goes to a third party.
- Works fully offline, so it fits air-gapped and on-prem machines.
- Free and GPL-3.0, with no per-job price. Fifty jobs cost the same as five.
- Schedule, policy and alerts in one TOML file you review in a pull request.
- A Run button, live logs, and a TUI over SSH to re-run the fix from where you noticed the failure.
- Supervises long-running services in the same file.
The simple case
A nightly backup. On the left, cron starts it and cronitor exec reports the outcome to Cronitor. On the right, RunWisp starts it and keeps the outcome itself.
# crontab, after "cronitor sync" wrapped the line
0 3 * * * cronitor exec nightly-backup /usr/local/bin/backup.sh # runwisp.toml: the daemon runs it and records the result
[tasks.nightly-backup]
cron = "0 3 * * *"
notify = ["slack-ops"]
run = "/usr/local/bin/backup.sh" The version that runs in prod
Three jobs, hardened. On the left, every line is wrapped and the rules live in the Cronitor dashboard. On the right, retries and timeouts are fields in the same file as the schedule. Add one heartbeat task to an outside monitor if a dead host is part of your threat model.
# crontab: cron schedules, cronitor exec reports
CRONITOR_API_KEY=<key>
0 3 * * * cronitor exec nightly-backup timeout 55m /usr/local/bin/backup.sh
*/5 * * * * cronitor exec api-probe curl -fsS https://api.example.com/health
0 4 * * 0 cronitor exec tmp-cleanup find /tmp -mtime +7 -delete
# Grace period, duration assertions, failure tolerance and
# alert routing are set per monitor in the Cronitor dashboard. # runwisp.toml: schedule, policy and alerts in one file;
# one external heartbeat watches the box itself.
[defaults]
notify = ["slack-ops"]
[tasks.nightly-backup]
cron = "0 3 * * *"
on_overlap = "skip"
timeout = "55m"
retry_attempts = 2
retry_backoff = "exponential"
keep_runs = 60
run = "/usr/local/bin/backup.sh"
[tasks.api-probe]
cron = "*/5 * * * *"
catch_up = 0
timeout = "30s"
retry_attempts = 2
run = "curl -fsS https://api.example.com/health"
[tasks.tmp-cleanup]
cron = "0 4 * * 0"
run = "find /tmp -mtime +7 -delete" Migration cheat sheet
Cronitor concepts on the left, runwisp.toml on the right. A dead-host alert is the one thing to keep outside.
| Cronitor | RunWisp |
|---|---|
cronitor exec <key> cmd | run = "cmd", no wrapper |
cronitor sync / discover | crontab -l | runwisp import cron |
| Monitor schedule + timezone | cron = + timezone = |
| Grace period / duration assertion | timeout = "55m"; an overrun fails the run and alerts |
| Failure tolerance | retry_attempts + retry_backoff: the job is tried again, not just tolerated |
| Follow-up alerts during an outage | Repeats grouped inside coalesce_window (default 1h) |
| Slack / Discord / Telegram / webhook integration | [notifiers.*] + notify = [...] |
| Output uploaded to Cronitor | Full stdout/stderr per run, kept locally |
| Missing-run alert for a dead host | Keep an outside heartbeat: one task that pings Cronitor or Healthchecks.io |
Gotchas
A runner can't report its own death
RunWisp catches failed runs, timeouts, crashes and firings missed while the daemon was down. If the whole machine goes dark, nothing on it is left to send the alert. Keep one external heartbeat: a five-minute RunWisp task that curls a Cronitor or Healthchecks.io ping URL. One monitor for the box instead of one per job fits inside Cronitor's free tier.
Your job data stays home
With a hosted monitor, when your jobs run, how long they took and what they printed sit on someone else's servers. RunWisp keeps all of it in a SQLite file on the box. For on-prem, regulated or air-gapped setups that can decide the question on its own.
No cross-host dashboard
RunWisp is one daemon per machine, each with its own UI. With ten servers you have ten dashboards and ten sets of alerts, which still arrive in one Slack channel. If one view over many hosts and Kubernetes is the point, Cronitor does that and RunWisp doesn't.
Run both during the switch
Move a job into runwisp.toml and keep the cronitor exec wrapper in its run line for a week. Both histories should agree. Then drop the wrapper and pause the monitor.
FAQ
What is a self-hosted alternative to Cronitor?
Does Cronitor run cron jobs?
How much does Cronitor cost compared to RunWisp?
Can RunWisp and Cronitor work together?
Does RunWisp have uptime monitoring?
Where does job output go?
Stop paying per cron job to find out it failed.
Install the binary, import your crontab, and keep one heartbeat for the box. Every run lands with its exit code, duration and output in your own dashboard.
curl -fsSL https://get.runwisp.com | sh