April 4, 2026 5 min read
How to add cron job monitoring without changing your infrastructure
You don't need a new server, a sidecar, or a logging pipeline to monitor your scheduled jobs. Three HTTP calls is the entire integration.
Most cron job monitoring advice involves significant infrastructure changes: configure a centralised logging stack, set up log forwarding, deploy a sidecar, integrate with your existing observability platform, write a custom alerting rule.
These are reasonable options for large teams with dedicated infrastructure engineers. For everyone else — a solo developer, a small team, a startup moving fast — the overhead of the setup is what prevents monitoring from happening at all. The result is production jobs running unmonitored for months until something visibly breaks.
There is a simpler model. Your jobs report their own status over HTTP. No new infrastructure. No log pipelines. No sidecar processes. Three HTTP calls per job run.
How the dead man's switch model works
Traditional monitoring is pull-based: a monitoring system checks whether something is alive. Dead man's switch monitoring is push-based: the thing being monitored announces its own health.
For cron jobs specifically:
- Your job sends an HTTP POST to a monitoring endpoint when it starts.
- Your job sends an HTTP POST when it completes successfully.
- If no start ping arrives within the expected window, an alert fires. If a start ping arrives but no success ping follows within the maximum duration, an alert fires.
That's the entire model. The monitoring service holds the expected schedule and grace periods. Your job holds nothing except the API key and the monitor ID.
No changes to your cron infrastructure. Your jobs run wherever they already run — a VPS, a container, a serverless function, a Raspberry Pi. The monitoring is entirely external.
The integration is three lines
In Node.js with the @crontify/sdk package:
import { CrontifyMonitor } from '@crontify/sdk';
const monitor = new CrontifyMonitor({
apiKey: process.env.CRONTIFY_API_KEY!,
monitorId: 'your-monitor-id',
});
await monitor.wrap(async () => {
await yourExistingJobFunction();
});
wrap() sends the start ping before calling your function, and the success or fail ping when it returns or throws. Your existing job function is unchanged.
For jobs that are not in Node.js, the same three pings are plain HTTP POST requests with an X-API-Key header:
# Start ping
curl -X POST https://api.crontify.com/api/v1/ping/your-monitor-id/start \
-H "X-API-Key: ck_live_your_key"
# Your job runs here
# Success ping
curl -X POST https://api.crontify.com/api/v1/ping/your-monitor-id/success \
-H "X-API-Key: ck_live_your_key"
In a shell script:
#!/bin/bash
set -e
MONITOR_ID="your-monitor-id"
API_KEY="${CRONTIFY_API_KEY}"
BASE_URL="https://api.crontify.com/api/v1/ping"
ping() {
curl -fsS -X POST "${BASE_URL}/${MONITOR_ID}/${1}" \
-H "X-API-Key: ${API_KEY}" > /dev/null 2>&1 || true
# || true prevents monitoring failures from killing the job
}
ping start
# Your existing job logic
/usr/local/bin/your-backup-script.sh
ping success
In Python:
import os
import requests
def ping(event: str, monitor_id: str):
try:
requests.post(
f"https://api.crontify.com/api/v1/ping/{monitor_id}/{event}",
headers={"X-API-Key": os.environ["CRONTIFY_API_KEY"]},
timeout=5,
)
except Exception:
pass # monitoring failure should never block the job
monitor_id = "your-monitor-id"
ping("start", monitor_id)
try:
your_existing_job()
ping("success", monitor_id)
except Exception as e:
ping("fail", monitor_id)
raise
What you get without touching your infrastructure
Missed run detection. Crontify parses your cron expression and knows when each run is expected. If no start ping arrives within your configured grace period, an alert fires. This catches missed runs caused by server reboots, cron daemon failures, and deleted crontabs — all situations where your existing logging would produce no evidence at all.
Hung job detection. If a start ping arrives but no success or fail ping follows within your maximum duration threshold, the run is marked as hung and an alert fires. This catches deadlocks, infinite loops, and network hangs that keep a process alive but not productive.
Run history. Every run is recorded with start time, end time, duration, and status. You get a dashboard showing run frequency, success rate, and duration trends over time.
Recovery alerts. When a monitor that was in a failing state receives a healthy ping, a recovery notification is sent automatically. You know when incidents are resolved, not just when they start.
Adding failure context without a logging pipeline
When a job fails, attaching the error context directly to the fail ping delivers it to your Slack or email alert without needing to configure log forwarding:
try {
await runJob();
await monitor.success();
} catch (err) {
await monitor.fail({
message: err.message,
log: err.stack, // up to 10,000 characters
});
throw err;
}
The first 500 characters appear inline in the Slack alert. The full log is stored in the run detail and accessible from the dashboard. This is not a replacement for a structured logging stack in large teams, but for a solo developer who doesn't have one, it means full failure context in the notification rather than a task to SSH into a server and find the log file.
The cost of not monitoring
The question is not whether monitoring is worth setting up. The question is whether the setup cost justifies it.
For infrastructure-heavy approaches, the answer is often no — until after the first incident that costs more than the setup would have. For the dead man's switch model with HTTP pings, the setup cost is under an hour for a first job. Once you have the pattern in one job, adding it to subsequent jobs is a few minutes each.
Crontify is free for up to 5 monitors — no credit card required.
Start monitoring your scheduled jobs
Free plan includes 5 monitors. No credit card required. Up and running in under 5 minutes.
Get started free →