RunWisp vs Healthchecks.io: a monitor, or a runner that monitors?
Healthchecks.io is a dead man's switch: your job pings a URL, and silence means trouble. RunWisp is the thing that runs the job, so it already knows the exit code, the duration and what got printed. They catch different failures, and the best setup often uses both.
Should you switch?
The 10-second version. Skim, decide, scroll down for the receipts.
Use RunWisp if
- You keep pasting a curl line onto every crontab entry, and keep forgetting one.
- You want the full output of the failed run, not the first 100 kB of a ping body.
- Retries, timeouts and overlap rules should happen, not just be noticed when they don't.
- Your box can't make outbound calls: air-gapped, on-prem, strict egress.
- You've run out of the 20 free checks and don't want a bill per job.
- You also need long-running services restarted when they crash.
Keep Healthchecks.io if
- You need to know when the whole server is dead. Nothing on the server can tell you that.
- Your jobs run on platforms you can't install a daemon on: a managed host, a NAS, a router, CI.
- You watch jobs across many machines from one dashboard.
- You want phone calls, SMS or PagerDuty for a missed run.
- The jobs already work and all you're missing is an alarm.
Yes / no, at a glance
Yes / no — and both columns win on something.
| Capability | Healthchecks.io | RunWisp |
|---|---|---|
| Runs the job | ✗cron or systemd does | ✓ |
| Exit code per run | ~if you ping /$? | ✓always |
| Captured output | ~POST body, 100 kB | ✓full log, per run |
| Works with no job changes | ✗curl per job | ✓ |
| Retries / timeouts / overlap policy | ✗ | ✓ |
| Run history | ✓ping log | ✓ |
| Alerts on failure | ✓27 integrations | ✓8 channels + inbox |
| Alerts on a job that never ran | ✓ | ~missed while daemon was down |
| Alerts when the whole host dies | ✓ | ✗pair with one check |
| One dashboard across many hosts | ✓ | ✗one daemon per host |
| Phone call / SMS alerts | ✓paid credits | ✗ |
| Works with no outbound internet | ~self-hosted on the LAN | ✓ |
| Open source | ✓BSD-3 | ✓GPL-3.0 |
| Self-hostable | ✓Django + DB | ✓single binary |
| Free with no job limit | ~20 checks hosted, unlimited self-hosted | ✓ |
| Long-running services | ✗ | ✓ |
Pros & cons
What Healthchecks.io does better
- Catches the failure nothing on the box can: power gone, kernel panic, network down, crond stopped.
- Works with any scheduler on any machine: cron, systemd timers, Kubernetes, CI, a NAS, a router.
- 27 integrations, including phone calls, SMS, WhatsApp, PagerDuty and Opsgenie.
- One dashboard for every job on every machine, with projects and team roles.
- Nothing to install on the server. A curl line and you're done.
- Open source (BSD-3) with an official Docker image, and run by a one-person company that has kept the free tier generous.
What RunWisp adds
- It runs the job, so “it ran” and “it succeeded” come from the process itself, not from a ping you hope fired.
- Every run's full stdout and stderr, kept per task and searchable. No body size limit to plan around.
- No curl to paste per job and nothing to forget: add a
[tasks.*]stanza and history and alerts are on. timeout,retry_attempts,on_overlapandcatch_upchange what happens, not just what gets reported.- Fails a run that printed
ERRORbut exited 0, withfailures = ['+output:ERROR']. - Works fully offline. Run times and output never leave the machine.
- A Run button, live logs and a TUI over SSH, so you can fix and re-run from the same place you noticed.
- Supervises long-running services in the same file. Free with no per-job limit.
The simple case
A nightly backup. On the left, cron runs it and the exit code goes to Healthchecks in the URL. On the right, RunWisp runs it and keeps the exit code, duration and output on its own.
# crontab: cron runs it, Healthchecks.io waits for the ping
0 3 * * * /usr/local/bin/backup.sh; \
curl -fsS -m 10 --retry 5 -o /dev/null \
https://hc-ping.com/<uuid>/$? # runwisp.toml: the daemon runs it and records the result
[tasks.backup]
cron = "0 3 * * *"
notify = ["telegram"]
run = "/usr/local/bin/backup.sh" The version that runs in prod
The setup we'd actually run. On the left, every job carries start and finish pings with its output in the body. On the right, RunWisp handles timeouts, retries and per-job alerts, and a single heartbeat task pings Healthchecks every five minutes. If the box dies, the pings stop and Healthchecks tells you. One check for the box instead of one per job.
# crontab: one check per job, start + exit-code pings, output in the body
0 3 * * * RID=$(uuidgen); \
curl -fsS -m 10 -o /dev/null "https://hc-ping.com/<uuid>/start?rid=$RID"; \
OUT=$(timeout 55m /usr/local/bin/backup.sh 2>&1); \
curl -fsS -m 10 --retry 5 -o /dev/null --data-raw "$OUT" \
"https://hc-ping.com/<uuid>/$??rid=$RID"
# Period, grace time and alert channels are set per check
# in the Healthchecks dashboard. # runwisp.toml: RunWisp runs and alerts on every job;
# one Healthchecks check watches RunWisp itself.
[tasks.backup]
cron = "0 3 * * *"
on_overlap = "skip"
timeout = "55m"
retry_attempts = 2
retry_backoff = "exponential"
keep_runs = 60
notify = ["telegram"]
run = "/usr/local/bin/backup.sh"
[tasks.heartbeat]
description = "Tell Healthchecks.io this box is alive"
cron = "*/5 * * * *"
catch_up = 0
secrets_file = "/etc/runwisp/hc.env" # HC_URL=https://hc-ping.com/<uuid>
run = 'curl -fsS -m 10 --retry 3 -o /dev/null "$HC_URL"' Migration cheat sheet
Healthchecks.io habits on the left, runwisp.toml on the right. Keep one check for the host.
| Healthchecks.io | RunWisp |
|---|---|
; curl hc-ping.com/<uuid>/$? on each line | Drop it. RunWisp records the exit code of every run. |
/start + finish ping for duration | Duration recorded per run, with CPU time and peak memory |
| POST body with the job's output | Full stdout/stderr per run, in the Web UI and TUI |
| Cron schedule + timezone on the check | cron = + timezone = on the task |
| Grace time | timeout = "55m"; an overrun fails the run |
| Missing ping alert | Firings missed while the daemon was down are recorded as missed and alert |
| Integrations (Slack, Telegram, ntfy, …) | [notifiers.*]: Slack, Discord, Telegram, ntfy, Gotify, Pushover, email, webhook |
| Alert on a dead server | Keep it: one heartbeat task pinging one Healthchecks check |
Gotchas
RunWisp can't report its own death
RunWisp runs on the box it watches. It catches a failed run, a timeout, a crash, and firings missed while the daemon was down. If the machine loses power or network, nothing is left to send the alert. That is exactly what a dead man's switch is for. Keep one Healthchecks.io check fed by a RunWisp heartbeat task, ideally on the hosted service or another machine.
One check for the box, not one per job
With RunWisp handling per-job failures, Healthchecks only has to answer “is this machine alive?” That's one check per host instead of one per job, so the 20-check free plan covers 20 servers instead of 20 jobs.
Fewer alert channels
Healthchecks.io lists 27 integrations, including phone calls, SMS and on-call tools. RunWisp ships Slack, Discord, Telegram, ntfy, Gotify, Pushover, email and a generic webhook. For PagerDuty or Opsgenie, point the webhook notifier at their events API, with a template_path to shape the body.
Nothing to keep in sync
A heartbeat setup has two sources of truth: the crontab and the check's schedule in the dashboard. Change one and forget the other, and you get false alarms or silence. In RunWisp the schedule and the monitoring are the same line.
FAQ
Is RunWisp a Healthchecks.io alternative?
Can I use RunWisp and Healthchecks.io together?
Can't Healthchecks.io show me the job's output too?
Is Healthchecks.io free?
Should I self-host Healthchecks.io on the same server as my jobs?
Does RunWisp alert when a job doesn't run at all?
Let the runner watch the jobs. Let a heartbeat watch the runner.
Install the binary, move your crontab into runwisp.toml, and keep one Healthchecks.io check for the box. Every run lands with its exit code and output.
curl -fsSL https://get.runwisp.com | sh