Monitor Heroku Scheduler jobs: detect failed and skipped tasks
The Heroku Scheduler add-on runs your task in a one-off dyno every 10 minutes, hourly, or daily. It's explicitly best-effort: a run can be delayed or skipped, and a task that exits non-zero produces no notification: the failure lives only in your logs. Append one curl ping to the job command and LastPing turns "hope it ran" into a signal you can trust.
Your assistant can do this for you
Monitor my Heroku Scheduler jobs with LastPing
Setup: append a ping to the job command
One config var and one edit in the Scheduler dashboard. Works for any buildpack.
Create a heartbeat monitor in LastPing
In the console, create a monitor matching your Scheduler frequency (every 10 min / hourly / daily). Heroku Scheduler runs in UTC: set the monitor to UTC. Give daily jobs a generous grace window since Scheduler is best-effort. Copy the ping URL.
https://ping.lastping.dev/<your-monitor-id>
Store the ping URL as a config var
Keep the URL out of the dashboard command by setting it as a config var:
heroku config:set LASTPING_PING_URL=https://ping.lastping.dev/<your-monitor-id> -a your-app
Append the ping to the Scheduler job command
In Resources → Heroku Scheduler → the job, add && curl … so the ping only fires on a clean exit. Add || curl …/fail to be paged immediately on failure:
# Scheduler job command
python manage.py send_digests \
&& curl -fsS -m 10 --retry 3 -o /dev/null "$LASTPING_PING_URL" \
|| curl -fsS -m 10 --retry 3 -o /dev/null "$LASTPING_PING_URL/fail"
Prefer a rake/management task? Wrap it in a script
If the command is complex, put it in bin/scheduled-digest in your repo, ping at the end of the script, and set the Scheduler command to bin/scheduled-digest. Same effect, cleaner dashboard.
What this catches
- Task exits non-zero: success ping skipped (or /fail fires); silence opens an incident.
- Scheduler skips a run: best-effort means runs can be dropped; no ping → incident opens.
- Dyno never boots: a failed release or bad config keeps the one-off dyno from starting; no ping → incident.
- Recovery: the next successful run closes the incident automatically.
On Render? See Render cron jobs. On Railway? See Railway cron jobs. Running Rails/Sidekiq? See Sidekiq. For the general pattern, see Monitor cron jobs.
Questions people ask
Front-loaded answers: the most important fact first.
-
Does Heroku Scheduler alert when a job fails?
No. Heroku Scheduler runs your task in a one-off dyno and does not notify you when it exits non-zero: the failure appears only in the app logs (
heroku logs), which you have to be watching. Add-ons like a log drain can surface it, but nothing tells you a run was skipped. A dead-man's-switch ping catches failures and skipped runs by expecting a check-in each time the job runs. -
Is Heroku Scheduler guaranteed to run my job on time?
No. Heroku documents Scheduler as best-effort: jobs usually run near the scheduled time, but can be delayed or skipped, and it is not guaranteed. For anything important (billing, backups, digests) that unreliability is exactly why an external watchdog matters: LastPing opens an incident when an expected run never checks in.
-
What timezone does Heroku Scheduler use?
UTC. All Scheduler times are interpreted in UTC regardless of your app's
TZconfig var, so set your LastPing monitor to UTC too. -
How do I add monitoring to a Heroku Scheduler job?
Append
&& curl -fsS -m 10 --retry 3 -o /dev/null $LASTPING_PING_URLto the job command in the Scheduler dashboard, and setLASTPING_PING_URLwithheroku config:set. Create a LastPing monitor with a matching schedule and grace window. -
Is this free?
Yes. LastPing is free for individuals with no monitor cap. See vs Healthchecks.io and vs Cronitor.
Best-effort scheduling deserves a real watchdog.
Append one curl to the job command. First alert in under a minute, and LastPing monitors itself the same way.