Send heartbeats reliably
The basic pattern is one line of curl. Production cron jobs need a few more precautions.
1. Ping at the end, and report failures
Section titled “1. Ping at the end, and report failures”# Wrong: pings even on partial failuredo_thingcurl $PING_URL
# Right: success pings the URL, failure adds /failcurl -fsS --max-time 10 "$PING_URL/start" || trueif do_thing; then curl -fsS --max-time 10 "$PING_URL" || trueelse curl -fsS --max-time 10 "$PING_URL/fail" || truefiOr send the exit code: do_thing; curl -fsS --max-time 10 "$PING_URL/$?" || true. A 0 is a success ping and anything up to 255 is a failure with that code.
/startrecords that a run began. It is not a ping, so a run that starts and never finishes still alerts once the interval and grace pass. The recent pings list shows how long each run took./failor a non-zero exit code takes the check down right away instead of waiting for a missed ping. The alert says the job reported a failure, with the exit code when you sent one.
2. Never let the ping fail the job
Section titled “2. Never let the ping fail the job”The ping is a notification, not part of the job’s correctness. If the network is flaky, the job still succeeded, so don’t let curl exit non-zero from the ping line cause your job’s exit code to lie.
# Bash: || true makes failures non-fatalcurl -fsS --max-time 10 --retry 3 "$PING_URL" || true# Python: try/except swallowstry: requests.post(PING_URL, timeout=10)except requests.RequestException: logging.warning("Failed to ping Site Qwality")3. Use timeouts and limited retries
Section titled “3. Use timeouts and limited retries”The ping endpoint can briefly fail, so:
- Timeout: 10 seconds is plenty.
- Retries: 2 or 3 with brief backoff. Don’t retry indefinitely, since that wedges the job.
- Backoff: linear or exponential up to ~30s total budget.
curl -fsS --max-time 10 --retry 3 --retry-delay 5 "$PING_URL"4. Add jitter to avoid thundering herds
Section titled “4. Add jitter to avoid thundering herds”If 200 of your jobs all run at exactly 02:00 UTC and all ping immediately, you’ll get a brief spike at the receiver. Add 0–60 seconds of jitter to staggered fleets:
sleep $((RANDOM % 60))curl ...(Skip this for one-off or low-volume jobs.)
5. Treat the ping URL as a secret
Section titled “5. Treat the ping URL as a secret”The URL contains your account ID and the cron job ID. Anyone who knows it can ping (and silence) the check. Store it in your secrets manager (Vault, AWS Secrets Manager, GitHub Actions secrets), not in shell scripts checked into git.
6. Distinguish your jobs in the receipt headers
Section titled “6. Distinguish your jobs in the receipt headers”The receiver records all incoming HTTP headers. Send a custom header so you can tell which deploy / pod / runner pinged:
curl -H "X-Backup-Host: $(hostname)" \ -H "X-Backup-Version: $APP_VERSION" \ "$PING_URL"Useful when several jobs share one check.
7. Don’t conflate cron checks with pipeline status
Section titled “7. Don’t conflate cron checks with pipeline status”A cron check answers “did this job run on schedule?” It’s not a substitute for a deploy pipeline status, a CI build status, or anything with multi-step success semantics. For those:
- Use HTTP checks against a
/healthendpoint your app exposes. - Use metrics with alert thresholds for queue depth, error rate, etc.
- Use logs for the audit trail of what the job actually did.