What is cron monitoring
Cron monitoring tells you when a scheduled job did not run or did not succeed, by having each successful run leave a signal and alerting you when the expected signal is missing.
Cron has no idea whether your job worked. It starts the command at the scheduled minute and moves on. If the script exits 1, the output goes to a local mail spool nobody reads. If the server is rebuilt and the crontab is not, nothing runs and nothing complains. The usual way founders find out is a customer asking why invoices stopped, or a restore that finds the newest backup is 41 days old.
How to monitor cron jobs
- Push: the job calls a monitor URL after it succeeds, and the monitor alerts when a call is late. Dedicated cron monitoring tools work this way. They take your schedule, add a grace period, and alert at the moment a run should have reported in and did not.
- Pull: the job leaves evidence, such as a marker file or a row with a timestamp, and an HTTP endpoint returns 503 once that evidence is older than it should be. Any uptime monitor can watch that endpoint.
- Either way, chain the signal with && so a failed run stays quiet. A cron monitor that hears from a job that crashed is worse than no monitor.
Watching a cron job with an HTTP check
Logdash watches cron jobs the pull way. Its push monitors expect a call in every 15-second window, which suits always-on workers, not a job that runs once an hour. So the hourly job touches a file, and a small endpoint turns the age of that file into a status code.
# Back up every hour. touch only runs when backup.sh exits 0.
0 * * * * /srv/app/bin/backup.sh && touch /var/lib/app/backup.ok// freshness.js - run next to the job: node freshness.js
const http = require('node:http');
const fs = require('node:fs');
const MARKER = '/var/lib/app/backup.ok';
const MAX_AGE_MS = 2 * 60 * 60 * 1000; // hourly job, alert after 2 hours of silence
http
.createServer((req, res) => {
let ageMs = Infinity;
try {
ageMs = Date.now() - fs.statSync(MARKER).mtimeMs;
} catch {
// no marker yet: the job has never succeeded
}
const fresh = ageMs < MAX_AGE_MS;
res.writeHead(fresh ? 200 : 503, { 'cache-control': 'no-store' });
res.end(fresh ? 'ok\n' : 'stale\n');
})
.listen(8080);It answers 200 while the last good run is under 2 hours old and 503 after that. Set the limit to one schedule interval plus your longest run, so a single slow night does not page you. If the app already has a health route, add the same file check there instead of running a second server.
Set it up
- Expose the check Run freshness.js on the box where the job runs, or fold the check into your existing health route, and make the URL reachable from the internet.
- Add an HTTP monitor Create a service in Logdash and give it that URL. Checks run every 5 minutes on the free plan, every minute on Builder and every 15 seconds on Pro, and each one records the status code and response time.
- Break the job Make backup.sh exit 1, or backdate the marker with touch -d "3 hours ago". On the next check the endpoint answers 503, the monitor flips to down, and a Telegram alert reaches your phone with the monitor name and the status code.
Cron monitoring tools
Logdash vs Healthchecks.io for cron jobs
| Feature | Logdash | Healthchecks.io |
|---|---|---|
| How a job reports | Touches a file, an HTTP check reads its age | Pings a URL, with optional start and fail signals |
| Knows the schedule | No, you encode it as a maximum age | Period or cron expression with timezone, plus grace time |
| Free plan | Five services, HTTP checks every 5 minutes | 20 jobs, 100 log entries each |
| Alert channels | Telegram and webhook | Email, Telegram, Slack, Discord and 20+ more |
| Uptime checks on the app itself | Same service, same dashboard | Not built, it only listens for pings |
| Self-hosting | AGPL-3.0, not a one-command install yet | BSD 3-clause, official Docker image |
When Healthchecks.io is the better pick
- Most of what you run is scheduled jobs. A tool that reads the cron expression alerts within the grace time of a missed run, with no endpoint to write.
- You have 15 jobs across 4 servers. One curl per crontab line beats one freshness check per job.
- You need to know a run started and never finished. Start and success signals measure duration, and a file timestamp cannot.
- You want the alert by email. Logdash sends Telegram messages and webhooks only.