PulseWatchPulsewatch

Creating and configuring a monitor

A monitor is one thing you want to know about. There are two kinds: a heartbeat, where your job tells PulseWatch it ran, and an uptime check, where PulseWatch fetches a URL. The failure a heartbeat prevents is the one nothing else catches, where cron stops firing a job entirely and there is no error anywhere because nothing ran to produce one.

Which kind you want

TypeHow it worksUse it for
heartbeatYour job requests a URL when it runs. Silence past the schedule is the failure.Cron jobs, backups, ETL, queue workers, anything scheduled. Also anything behind a firewall, since nothing is fetched from outside.
uptimePulseWatch fetches a URL on a schedule and judges the response.Public endpoints, health checks, certificates. Anything you can reach from the internet.

Creating a heartbeat monitor

  1. Sign up and open the dashboard. A Free account can hold 3 monitors, which is enough to start.
  2. Choose New monitor and then Heartbeat.
  3. Name it after the job, not after the machine. Alerts are read at 3am by someone deciding whether to get up.
  4. Set how often it runs. A nightly backup is every 24 hours.
  5. Set grace. This is the part people get wrong: it should cover the slowest run you would still call normal, not the average one. A backup that usually takes 8 minutes and occasionally takes 25 wants at least 30 minutes of grace, or it will page for being slow.
  6. Save. The monitor page shows a ping URL. Copy it.
  7. Add the ping to the job.
    bash
    # The one line to add. --retry 5 so a dropped packet is not an outage,
    # -m 10 so the ping can never hang the job, -fsS so failures are visible.
    0 3 * * * /opt/backup.sh && curl -fsS -m 10 --retry 5 \
      https://pulsewatcher.up.railway.app/ping/<token>
  8. Confirm it worked: run the curl by hand now. The monitor moves from new to up within a few seconds and the dashboard row reads:
    text
    Nightly backup            up      every 24h 路 30m grace
                                      last ping 03:00:12 (7h ago)
                                      next expected by 03:30 tomorrow
    If it stays in new, the request did not arrive. Run the curl without -s to see why.

When the job stops running, the alert says what was expected and when it last happened:

text
馃敶 Nightly backup is down
reason: no_ping

Expected by 03:30. Last ping was 26h ago, at 03:00 yesterday.

https://pulsewatcher.vercel.app/app/monitors/<id>

Creating an uptime monitor

  1. Choose New monitor and then Uptime.
  2. Enter the URL. Prefer a health endpoint that checks the database over a homepage that renders from cache, because the homepage will be up during an outage.
  3. Set the check interval. The floor is 300 seconds on Free and 60 on Pro and Business.
  4. Leave the expected status range at 200 to 399 unless your endpoint deliberately returns something else.
  5. On Pro and above, add expected text. A string that only appears when the page is genuinely working turns a status check into a content check, and catches the 200 that renders "database unavailable".
  6. Confirm it worked: the first check runs within one interval. The monitor page then shows the status code, the latency, and, for an https URL, the certificate expiry date.

Schedules that an interval cannot express

"Every 24 hours" and "at 3am" are different statements. A job on a period is expected 24 hours after its last ping, so a run that drifts an hour late drags the whole schedule with it. A cron schedule expects it at 3am regardless.

Cron also expresses the things intervals cannot: weekdays only, the first of the month, twice a day at fixed times.

text
0 3 * * 1-5      weekdays at 03:00
0 */4 * * *      every four hours, on the hour
0 2 1 * *        the first of the month at 02:00
30 6,18 * * *    twice a day, 06:30 and 18:30

Set the timezone alongside the expression. A job scheduled in local time on a server that observes daylight saving will otherwise appear to be an hour late twice a year. Cron schedules need Pro or above.

Heartbeat fields

FieldTypeDefaultRangeEffectPlan
nametext(required)up to 80 charactersWhat alerts call it.all
schedule_kindenumperiodperiod, cronA fixed interval, or a cron expression for schedules an interval cannot express.cron needs Pro+
period_secondsint(required)30 to 31,536,000How often the job is expected to ping.all
grace_secondsint(required)0 to 31,536,000How late a ping may be before the monitor goes down.all
cron_expressiontextnull1 to 100 charactersStandard five-field cron. Required when schedule_kind is cron.Pro+
cron_timezonetextnullIANA nameWhich clock the expression is read in. Required alongside the expression.Pro+
max_duration_secondsintnull1 to 86,400A run open longer than this goes down. Needs a /start ping.all

Uptime fields

FieldTypeDefaultRangeEffectPlan
nametext(required)up to 80 charactersWhat alerts call it.all
urltext(required)http or httpsThe URL fetched on each check.all
check_interval_secondsint(required)60 to 86,400How often the URL is fetched. The floor your plan actually allows is 300 on Free and 60 on Pro and Business.all
timeout_msint10,0001,000 to 60,000A slower response counts as a timeout.all
expected_status_minint200100 to 599Bottom of the status range treated as healthy.all
expected_status_maxint399100 to 599Top of that range. Anything outside it is http_error.all
expect_texttextnull1 to 200 charactersThe body must contain this. Catches a 200 that renders an error page.Pro+
forbid_texttextnull1 to 200 charactersThe body must not contain this.Pro+
degraded_threshold_msintnull1 to 60,000Slower than this and the monitor is flagged degraded while still counting as up.Pro+

Certificate expiry warnings are automatic on Pro and above for https URLs. There is no field: PulseWatch reads the certificate on each check and alerts once inside the warning window, with reason ssl_expiring.

Alerting fields, on both types

FieldTypeDefaultRangeEffectPlan
alert_after_secondsint00 to 86,400Wait this long after a failure before sending anything. Absorbs a blip that fixes itself.Pro+
repeat_interval_secondsint0 (off)0, or 900 to 604,800Re-send while still down. Acknowledging the incident stops the repeats.Pro+
alert_on_new_errorbooleantrueAlert the first time a stack-trace fingerprint is seen, even while already down.Pro+
escalate_after_consecutiveintnull (off)1 to 100Alert when the same error fingerprint fails this many runs in a row.Pro+
warn_deliveryenumdigest_dailyimmediate, digest_daily, silentWhen warnings reach you.all
warn_escalate_afterint31 to 100Consecutive warns after which the monitor goes down with persistent_warnings.all
tagstext[]noneFilter and group on the dashboard.all
on_status_pagebooleanfalsePublish this monitor on your status page.Pro+
runbook_mdtext'' (account default)up to 20,000 charactersInstructions delivered inside the alert.all
runbook_urltextnullhttp or https, up to 500 charsA link carried on the alert.all

Pausing rather than deleting

A paused monitor still accepts pings and still records them. It simply never changes state and never alerts, and the paused period is excluded from the uptime calculation rather than counted as either up or down. Use it while you are working on the job. Deleting throws away the history.

Monitors can also pause themselves. Dropping to a plan with a smaller cap pauses the oldest monitors until the count fits, newest left running, and nothing is deleted. Upgrading resumes exactly those.

Variations

A nightly backup

Heartbeat, 24 hour period, 30 minute grace, a metric floor on rows written. The schedule catches the job that stopped; the floor catches the job that runs and writes nothing.

A job that runs every five minutes

Heartbeat, 300 second period, 120 second grace, alert_after_seconds of 300. One missed run on a five-minute job is usually noise; two in a row is not.

A checkout API

Uptime, 60 second interval, expected text matching a string only present when the database is reachable, degraded threshold at 2000ms. The degraded flag gives you the slowdown before the timeout.

A weekday-only report

Heartbeat on the cron schedule 0 7 * * 1-5. A period-based monitor would report it missing every Saturday.

A long ETL that sometimes hangs

Heartbeat with /start, max_duration_seconds set, and overlap detection on. Catches the run that never finishes, not just the run that never starts.

What can go wrong

Alerts every time the job is slightly slow

Grace is set to the average run rather than the slowest normal one. The pattern is an alert followed by a recovery a few minutes later, repeatedly. The advisor flags it.

The job fails and PulseWatch says nothing

job.sh && curl sends a ping only on success, so a failure looks like silence and waits for the schedule. Report the outcome with /exit/$? instead.

A cron monitor is late by exactly one hour, twice a year

The timezone is UTC while the server runs local time. Set the monitor's timezone to match the machine.

An uptime check passes during an outage

The URL is served from cache or a CDN and never touches the thing that is broken. Point it at a health endpoint that exercises the database, and add expected text.

The check interval will not go below five minutes

That is the Free floor. Pro and Business allow 60 seconds.

Related

  • Pinging API for every ping variant and language.
  • Fleets when the same job runs on many machines. One fleet instead of 200 monitors.
  • How alerting works for what happens once a monitor goes down.
  • Exit codes to report how a run ended rather than only that it happened.