How to Monitor Cron Jobs and Background Tasks
Cron jobs fail silently. HTTP monitors can't watch them. Heartbeat monitoring is the right tool — here's how to set it up in Node.js, Python, Go, and shell scripts, with code examples for each.
Uptime monitoring checks that your HTTP endpoints respond. But a large class of critical jobs have no HTTP endpoint to check — they run on a schedule, do their work, and exit. If they stop running, nothing alerts you.
The classic failure mode: a nightly database backup job that silently stopped running three weeks ago. You find out when you need to restore from backup and discover the most recent one is three weeks old.
Heartbeat monitoring inverts the check. Instead of an external service pinging your endpoint to see if it's up, your job pings an external service to say "I ran successfully." If the ping doesn't arrive within the expected window, you get an alert.
How heartbeat monitoring works
The setup has three parts:
- You create a heartbeat monitor in PingBase with a schedule (e.g., "every 1 hour") and a grace period (e.g., "5 minutes late is OK").
- PingBase gives you a unique URL for that monitor — your ping URL.
- You add a single HTTP request to the end of your job that hits that URL when the job completes successfully.
If the ping doesn't arrive within the schedule plus grace period, PingBase marks the monitor as down and sends you an alert through your configured channels.
The ping URL looks like: https://app.pingba.se/hb/your-unique-token
Shell scripts and cron jobs
The simplest case. Add a curl call at the end of your script, after the main work completes successfully:
# backup.sh
#!/bin/bash set -e # Your backup logic pg_dump mydb | gzip > /backups/mydb-$(date +%Y%m%d).sql.gz aws s3 cp /backups/mydb-$(date +%Y%m%d).sql.gz s3://my-backups/ # Ping PingBase on success curl -fsS --retry 3 https://app.pingba.se/hb/YOUR_TOKEN > /dev/null
Key flags: -f fails on HTTP errors, -s suppresses progress output, -S still shows errors, --retry 3 retries on transient network failures. The > /dev/null discards the response body.
The set -e at the top ensures the script exits immediately if any command fails — so the ping at the bottom only runs if everything before it succeeded.
For your crontab:
# crontab -e
0 2 * * * /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1
Node.js
For a Node.js worker or scheduled task, add the ping at the end of your job function:
# worker.js
const HEARTBEAT_URL = process.env.PINGBASE_HEARTBEAT_URL;
async function runJob() {
// Your job logic
await processQueue();
await sendReports();
// Ping PingBase on success
if (HEARTBEAT_URL) {
try {
await fetch(HEARTBEAT_URL);
} catch (err) {
// Don't fail the job if the ping fails
console.warn('Heartbeat ping failed:', err.message);
}
}
}
runJob().catch(err => {
console.error('Job failed:', err);
process.exit(1);
});
Store the heartbeat URL in an environment variable — don't hardcode it. This makes it easy to disable in development and rotate the token if needed.
For jobs using a scheduler like node-cron:
import cron from 'node-cron';
cron.schedule('0 * * * *', async () => {
try {
await processHourlyJob();
// Ping on success
await fetch(process.env.PINGBASE_HEARTBEAT_URL);
} catch (err) {
console.error('Job failed:', err);
// Don't ping — missing ping = alert
}
});
Python
Python's standard library includes urllib, but requests is simpler for this use case:
# job.py
import os
import requests
HEARTBEAT_URL = os.environ.get('PINGBASE_HEARTBEAT_URL')
def ping_heartbeat():
if not HEARTBEAT_URL:
return
try:
requests.get(HEARTBEAT_URL, timeout=10)
except Exception as e:
print(f"Heartbeat ping failed: {e}")
def run_job():
# Your job logic
process_data()
generate_reports()
send_emails()
# Ping on success
ping_heartbeat()
if __name__ == '__main__':
try:
run_job()
except Exception as e:
print(f"Job failed: {e}")
raise # Don't ping — missing ping = alert
If you don't want to add requests as a dependency, use the standard library:
import urllib.request
def ping_heartbeat():
if not HEARTBEAT_URL:
return
try:
urllib.request.urlopen(HEARTBEAT_URL, timeout=10)
except Exception as e:
print(f"Heartbeat ping failed: {e}")
For Django management commands or Celery tasks, add the ping at the end of the handle() method or task function after all work has completed.
Go
# main.go
package main
import (
"fmt"
"net/http"
"os"
"time"
)
func pingHeartbeat() {
url := os.Getenv("PINGBASE_HEARTBEAT_URL")
if url == "" {
return
}
client := &http.Client{Timeout: 10 * time.Second}
resp, err := client.Get(url)
if err != nil {
fmt.Fprintf(os.Stderr, "heartbeat ping failed: %v\n", err)
return
}
resp.Body.Close()
}
func runJob() error {
// Your job logic
if err := processData(); err != nil {
return fmt.Errorf("processData: %w", err)
}
if err := sendReports(); err != nil {
return fmt.Errorf("sendReports: %w", err)
}
return nil
}
func main() {
if err := runJob(); err != nil {
fmt.Fprintf(os.Stderr, "job failed: %v\n", err)
os.Exit(1)
// Don't ping — missing ping = alert
}
pingHeartbeat()
}
The pattern is the same across languages: ping only on success, let failure be silent. The missing ping is the alert.
Choosing the right schedule and grace period
When creating a heartbeat monitor in PingBase, you set two values:
- Period: How often the job should run. Set this to match your cron schedule exactly. If your job runs every hour, set the period to 60 minutes.
- Grace period: How late the ping can arrive before the monitor goes down. For a job that sometimes takes variable time (e.g., processing depends on data volume), set a generous grace period. For time-critical jobs, set it tight.
| Job type | Period | Grace period |
|---|---|---|
| Every-minute health worker | 1 min | 1 min |
| Hourly data sync | 60 min | 10 min |
| Nightly backup | 24 hrs | 30 min |
| Weekly report generation | 7 days | 2 hrs |
| Queue processor (continuous) | 5 min | 5 min |
Jobs worth monitoring with heartbeats
Any job that runs on a schedule and produces no user-visible output is a heartbeat monitoring candidate. Common examples:
- Database backups
- Data sync jobs (pulling from external APIs, syncing to data warehouse)
- Email digest and notification jobs
- Cleanup jobs (deleting old records, purging files)
- Report generation
- Cache warming
- SSL certificate renewal (e.g., Certbot)
- Invoice generation and billing jobs
- Search index rebuilds
- Analytics aggregation
If the job failed silently for a week before you found out, would you care? If yes, add a heartbeat monitor.
Monitor your cron jobs with PingBase
Heartbeat monitors for background tasks are included on all plans. Free for up to 5 monitors.
Get started free →