Guide: heartbeat monitors
Know when a cron job, backup or scheduled task silently stops
Most scheduled jobs fail quietly: the backup stops running and nobody notices until the day it is needed. A heartbeat monitor waits for your job to check in, and alerts you when it does not.
How it works
Website and server monitors check from the outside. A job that runs on its own schedule has nothing to visit, so a heartbeat works the other way around: the job calls Monitorizer.
Your job checks in
Each heartbeat has a private address. Your cron job, backup or script calls it when it finishes, with curl, PowerShell or any HTTP request.
Monitorizer knows when to expect it
You say when the job runs: every 15 minutes, every day, every Monday, the 1st of each month, or any cron schedule.
You are alerted when it does not
When a call is late, the job reports a failure, or a started job never finishes, your Mac alerts you on your usual channels.
Set one up in three steps
- 1
Add a heartbeat monitor
On the website: Monitors, Add monitor, Heartbeat. In the Mac app: Add, Heartbeat. Give it a clear name, such as "Nightly backup, shop server". Heartbeats need your account, since the calls arrive on the website.
- 2
Paste your crontab line
Paste the job's line from crontab -e: the schedule, a name and a grace time are filled in for you. No crontab line (Windows, GitHub Actions)? Set the interval or schedule by hand. Check the time zone: the one of the machine that runs the job.
- 3
Copy the line back
Save. The monitor's page gives your line back with the ping added, in a simple version or one that also reports failures. Replace the old line with it, and the first ping appears within seconds of the next run.
Interval or cron schedule?
At a fixed interval
For jobs that run every N minutes, hours, days or weeks, counted from the last call. If the job runs every 6 hours, choose 6 hours. The next call is expected 6 hours after the last one, whatever time it came.
On a cron schedule
For jobs that run at set times: every day at 03:00, weekdays only, every Monday, the 1st or the last day of each month. No fixed interval matches a monthly job (months have 28 to 31 days), so use a schedule. Paste the same five fields as in your crontab.
A cron schedule has five fields, separated by spaces:
┌───────── minute (0 to 59)
│ ┌─────── hour (0 to 23)
│ │ ┌───── day of the month (1 to 31, L = last)
│ │ │ ┌─── month (1 to 12)
│ │ │ │ ┌─ day of the week (0 to 6, Sunday = 0)
│ │ │ │ │
0 3 1 * *| Schedule | Meaning |
|---|---|
*/5 * * * * | Every 5 minutes |
0 * * * * | Every hour |
0 3 * * * | At 03:00 |
30 8 * * 1-5 | At 08:30, Monday through Friday |
0 3 * * 1 | At 03:00, only on Monday |
0 3 1 * * | At 03:00, on day 1 of the month |
0 3 L * * | At 03:00, on the last day of the month |
0 4 1 */3 * | At 04:00, on day 1 of the month, every 3 months |
*/15 9-17 * * 1-5 | Every 15 minutes, between 09:00 and 17:59, Monday through Friday |
Choose the time zone of the machine that runs the job, so "03:00" means the same thing on both sides. Daylight saving time is handled for you.
Choosing the grace time
The grace time is how long after the expected time a call may still arrive. It covers the time the job takes and small delays. Too short gives false alarms, too long delays real ones.
- A quick job (a few seconds) every 5 to 15 minutes: 5 minutes.
- An hourly job that takes a few minutes: 15 to 30 minutes.
- A nightly backup that takes 20 minutes: 1 hour.
- A weekly or monthly job that can take hours: 2 to 6 hours.
- Rule of thumb: the longest the job has ever taken, plus a margin.
The ping address
GET, POST and HEAD requests all work, so any tool that can open a web address can ping.
/hb/<address> | The job finished well. This is the only call you need. |
/hb/<address>/start | Optional, when the job starts. You then see how long each run took, and a job that started but never finished is flagged. |
/hb/<address>/fail | The job failed. You are alerted right away instead of waiting for the grace time. |
POST body or ?msg= | Anything the job sends (for example the end of its log) is kept with the ping and shown in the alert. The last 1,000 characters are kept. |
Keep the address private, like a password: anyone who has it can send calls for this monitor. If it leaks, delete the monitor and create a new one, which gets a new address.
Examples
Replace YOUR-ADDRESS with the address shown on your monitor's page (it has copy buttons).
# The call is made only if the backup succeeded (&&).
0 3 * * * /home/me/backup.sh && curl -fsS -m 10 --retry 3 -o /dev/null https://monitorizer.ai.tn/hb/YOUR-ADDRESS#!/bin/sh
# Reports the start, the end, and a failure with the end of the log.
HB=https://monitorizer.ai.tn/hb/YOUR-ADDRESS
curl -fsS -m 10 -o /dev/null $HB/start
if /home/me/backup.sh > /tmp/backup.log 2>&1; then
curl -fsS -m 10 -o /dev/null $HB
else
tail -c 1000 /tmp/backup.log | curl -fsS -m 10 -o /dev/null --data-binary @- $HB/fail
fi# /etc/systemd/system/backup.service
[Service]
Type=oneshot
ExecStart=/home/me/backup.sh
ExecStartPost=/usr/bin/curl -fsS -m 10 -o /dev/null https://monitorizer.ai.tn/hb/YOUR-ADDRESS# Run this script from the scheduled task.
& "C:\Scripts\backup.ps1"
if ($?) { Invoke-RestMethod https://monitorizer.ai.tn/hb/YOUR-ADDRESS }
else { Invoke-RestMethod -Method Post -Body "Backup failed" https://monitorizer.ai.tn/hb/YOUR-ADDRESS/fail }# .github/workflows/nightly.yml
on:
schedule:
- cron: "0 3 * * *"
jobs:
nightly:
runs-on: ubuntu-latest
steps:
- run: ./nightly.sh
- run: curl -fsS -m 10 ${{ secrets.HEARTBEAT_URL }}
- if: failure()
run: curl -fsS -m 10 ${{ secrets.HEARTBEAT_URL }}/fail<?php
// At the end of the scheduled task.
run_my_task();
file_get_contents('https://monitorizer.ai.tn/hb/YOUR-ADDRESS');
// WordPress: wp_remote_get('https://monitorizer.ai.tn/hb/YOUR-ADDRESS');try {
await runJob();
await fetch("https://monitorizer.ai.tn/hb/YOUR-ADDRESS");
} catch (error) {
await fetch("https://monitorizer.ai.tn/hb/YOUR-ADDRESS/fail", { method: "POST", body: String(error) });
}import urllib.request
try:
run_job()
urllib.request.urlopen("https://monitorizer.ai.tn/hb/YOUR-ADDRESS", timeout=10)
except Exception as error:
urllib.request.urlopen("https://monitorizer.ai.tn/hb/YOUR-ADDRESS/fail", data=str(error).encode(), timeout=10)Questions
What happens if my Mac is off?
The calls are still received and kept by the website. Alerts are sent by your Mac, so a missed call is reported as soon as the Mac is back on. To be alerted around the clock, keep a Mac that is always on signed in to your account.
What if the job fails but still calls the address?
Place the call after the job with && (or check the result, as in the examples), so it is made only when the job succeeded. Or call /fail when it fails, to be alerted at once.
Does a heartbeat count in my plan?
Yes, each heartbeat counts as one monitor.
Can I see the past calls?
The monitor's page lists the last calls with their time, result, duration, message and the address they came from. Its uptime and incidents work like any other monitor.
Can I change the schedule later?
Yes, on the website or in the Mac app. The ping address stays the same, so the job does not need to change.
Is there a limit on calls?
Up to 120 calls a minute per address, far more than any scheduled job needs.
Watch your first job in two minutes
Add a heartbeat, paste one line into your crontab, and stop wondering whether last night's backup ran.
Create a heartbeat