How to Monitor Cron Jobs That Fail Silently
Sources and verification
- Source dates
- Oldest source check: 10 September 2026.
- Technical verification
- Separate technical verification has not been recorded.
Publication approval and technical verification are recorded separately. Automated link checks establish reachability, not accuracy. A checked example verifies only its stated test cases, not the whole article or your production setup.
In short
Monitor the job outcome against a deadline, independently of server uptime. Send success only after the command and its output checks pass; capture a failure when they do not. An outside deadline check catches jobs that never start. Test missing runs, nonzero exits, invalid output, long runtimes and lost heartbeat delivery before trusting a green check for a backup or report.
Put this guide to work
Example verification · 10 September 2026: Wrapper syntax and four local cases checked: job failure, invalid output, heartbeat failure and success. Commands and heartbeat URL are placeholders; scheduler/deadline behavior still needs your own test.
Jump to a section
Key takeaways
- A healthy server does not prove a scheduled job ran.
- Exit code zero is insufficient when the output can still be wrong.
- A deadline detector catches a disabled scheduler that cannot send a failure.
Define what completion means
Write one sentence before adding monitoring: “Yesterday's export exists, contains the expected reporting date, and can be read by the downstream system before its deadline.” For a backup, distinguish a new archive from a verified restore. A daily success ping cannot establish restoreability by itself.
Create one check per independently actionable job. If ten jobs share one heartbeat URL, one successful job can conceal nine missing jobs.
Wire success after verification
Healthchecks' cron documentation (opens in a new tab) describes sending a request when a job completes. The following Bash wrapper adds an application-specific verification step. Replace both commands and load HC_URL from your scheduler's restricted environment; it is the complete secret ping URL. Do not paste it into a public issue.
#!/usr/bin/env bash
set -u
: "${HC_URL:?Set the complete success heartbeat URL}"
if /opt/jobs/export-report; then
if /opt/jobs/verify-report; then
if curl --fail --silent --show-error --max-time 10 \
--retry 2 "$HC_URL" >/dev/null; then
exit 0
fi
printf '%s\n' 'Job output verified, but heartbeat delivery failed' >&2
exit 3
fi
printf '%s\n' 'Job finished, but output verification failed' >&2
exit 2
fi
printf '%s\n' 'Job command failed' >&2
exit 1
The two /opt/jobs/ programs are placeholders you must implement. The verifier should check freshness and the expected reporting period, not merely whether a file left over from yesterday exists. Log a job identifier and run identifier with failures, without logging credentials or report contents.
This wrapper sends only success. The receiver's missed-deadline alert is therefore essential. A provider-specific failure endpoint can add earlier failure notification, but never replace the deadline. Preserve the original command's exit code too if another system depends on its exact value.
Set the expected schedule
Configure the receiver with the intended schedule, its time zone, and a grace period based on observed normal runtime plus delivery jitter. For a job with a business deadline, monitor that deadline rather than just “once per day.” Confirm how your scheduler handles daylight-saving changes and overlapping runs.
For example, a report due at 06:00 needs a late-report alert early enough for a person to recover it. A receiver that waits until tomorrow to call it missing is correctly configured for the wrong operational requirement.
Run the failure matrix
Use a test job and a separate test heartbeat URL.
| Test | Expected result | Evidence |
|---|---|---|
| Scheduler disabled | No success; deadline alert | Last successful run and alarm time |
| Command exits nonzero | No success heartbeat | Failure log and deadline alert |
| Command returns zero with stale output | Verifier rejects output | Verification failure and no new success |
| Command exceeds deadline | Late alert despite a running process | Runtime and receiver deadline |
| Valid output, receiver unreachable | Delivery failure, no false success | Local exit 3 and missed report |
| Next valid run | Receiver records recovery | Recovery time and matching run |
Handle the confusing case
“The job succeeded but monitoring says it failed” can mean that heartbeat delivery failed. Check the job's verified output and the receiver's last accepted request separately. Retrying the business job may duplicate an invoice or export; retry notification independently when that is appropriate.
Keep concurrency control, timeout behavior and restore tests in the job's runbook. Monitoring a job does not make rerunning it safe.
Did this help?
Your answer helps us improve this guide. We save only the page and your choice for 30 days.
No name, email, or incident details are requested.
Frequently asked
- Should the ping run before or after the job?
- Success belongs after the job and output verification. A separate start signal can measure duration, but it is not a success signal.
- What if cron never launches the wrapper?
- An independently configured deadline detector must alert when success does not arrive. Code inside a job cannot report a run that never started.
Sources
Vendor facts change. Each source below shows the date this page last checked it.
- Monitoring cron jobs — Healthchecks. Checked 10 September 2026.
- Configuring checks — Healthchecks. Checked 10 September 2026.
Related
One practical idea, occasionally
The On-Call Brief: short field notes, templates, and operational lessons.
Follow the field guide
Email subscriptions are not open yet. Read new guides in your feed reader — no email address needed.
Subscribe with RSS