Get started in 2 minutes
Full REST API documentation
Public status pages for your users
CronOwl is a dead man's switch monitoring service for your scheduled tasks and cron jobs. Instead of actively checking if your servers are up, CronOwl waits for your jobs to "check in" by sending a simple HTTP request (ping).
If a ping doesn't arrive within the expected time window, CronOwl knows something is wrong and immediately alerts you via email, push notifications, Telegram, or webhooks.
Why "dead man's switch"? The concept comes from safety devices that trigger when the operator becomes incapacitated. Similarly, CronOwl triggers alerts when your job stops "checking in" — meaning something has gone wrong.
Go to your Dashboard and click "New Check". Give it a descriptive name and set the expected schedule.
Example schedules:
every 5 minutes — For health checksevery hour — For hourly reportsevery day at 2am — For nightly backups0 2 * * * — Custom cron expressionEach check gets a unique URL. Copy it from the dashboard — it looks like this:
https://cronowl.com/api/ping/abc123xyzAdd a single curl request at the end of your cron job. When CronOwl receives the ping, it knows your job ran successfully.
#!/bin/bash
set -e
# Your backup logic
pg_dump mydb > /backups/mydb_$(date +%Y%m%d).sql
# Ping CronOwl on success
curl -fsS --retry 3 https://cronowl.com/api/ping/YOUR_SLUGIf your job doesn't ping within the expected time + grace period, we'll alert you immediately:
At the end of your script, make an HTTP request to your unique ping URL. CronOwl records the timestamp, duration, and any metadata you send.
Based on your configured schedule (e.g., "every hour"), CronOwl knows when the next ping should arrive. It continuously monitors all your checks.
Jobs don't always run at exactly the same time. The grace period (1-60 minutes) gives your job extra time before CronOwl considers it late.
If the expected time + grace period passes without a ping, CronOwl immediately sends alerts through all your configured channels.
When a previously "down" check receives a ping again, CronOwl sends a recovery notification so you know the issue is resolved.
Check created but no pings received yet. Waiting for first ping.
Pings arriving on schedule. Everything is working correctly.
Ping is late. Expected time + grace period has passed without a ping.
Choose from common intervals for quick setup:
For precise control, use standard cron syntax:
* * * * *| Expression | Description |
|---|---|
*/5 * * * * | Every 5 minutes |
0 * * * * | Every hour at minute 0 |
0 2 * * * | Every day at 2:00 AM |
0 9 * * 1-5 | Every weekday at 9:00 AM |
0 0 1 * * | First day of every month at midnight |
30 4 * * 0 | Every Sunday at 4:30 AM |
All schedules are evaluated in your selected timezone. Supported timezones include:
And 20+ more timezones available in the dashboard.
The grace period (1-60 minutes) is extra time added after the expected ping time before marking a check as "down". This accounts for:
Example: If your job runs at 2:00 AM with a 5-minute grace period, CronOwl won't alert until 2:05 AM if no ping is received.
CronOwl sends notifications through multiple channels. Configure them in your Settings.
All plans
Receive detailed email alerts for down, recovery, and slow job events. Emails are sent to your account email address.
Enable in Settings → Notifications → Email notifications
All plans
Get instant push notifications on your phone or desktop. Works even when the browser is closed. CronOwl is a PWA — install it on your home screen for the best experience.
Enable in Settings → Notifications → Push notifications (requires browser permission)
All plans
Receive alerts directly in Telegram. Link your account with a simple verification code.
How to connect:
Starter & Pro plans
Send alerts to any HTTP endpoint. Includes native formatting for Slack and Discord.
Use a Slack Incoming Webhook URL. Messages are formatted with colored attachments.
https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXUse a Discord Webhook URL. Messages use rich embeds with colors.
https://discord.com/api/webhooks/000000000000000000/XXXXXXXXAny other URL receives a JSON POST request:
{
"event": "check.down",
"check": {
"id": "abc123",
"name": "Database Backup",
"slug": "db-backup-xyz",
"status": "down"
},
"timestamp": "2024-01-20T02:10:00Z",
"message": "Check 'Database Backup' is DOWN"
}Sent when a check misses its expected ping time + grace period.
Sent when a previously down check receives a ping and is back up.
Sent when a job's duration exceeds the configured maximum duration (maxDuration).
Create public status pages to share the health of your services with your team or customers.
Embed a live status badge in your README or website:
[(https://cronowl.com/status/YOUR_SLUG)<a href="https://cronowl.com/status/YOUR_SLUG">
<img src="https://cronowl.com/api/status/YOUR_SLUG/badge" alt="Status">
</a>Keep your users informed during outages by creating and managing incidents on your status pages.
Update the incident status as you progress through the investigation and resolution.
#!/bin/bash
set -e
# Signal job start (optional)
curl -fsS "https://cronowl.com/api/ping/YOUR_SLUG?start=1"
START_TIME=$(date +%s%3N)
# Your backup logic here
pg_dump mydb > /backups/mydb_$(date +%Y%m%d).sql
gzip /backups/mydb_$(date +%Y%m%d).sql
END_TIME=$(date +%s%3N)
DURATION=$((END_TIME - START_TIME))
# Signal completion with duration
curl -fsS "https://cronowl.com/api/ping/YOUR_SLUG?duration=$DURATION&exit_code=$?"# Run backup every day at 2am and ping CronOwl
0 2 * * * /path/to/backup.sh && curl -fsS https://cronowl.com/api/ping/YOUR_SLUG
# Alternatively, ping even on failure to track exit code
0 2 * * * /path/to/backup.sh; curl -fsS "https://cronowl.com/api/ping/YOUR_SLUG?exit_code=$?"import requests
import time
CRONOWL_URL = "https://cronowl.com/api/ping/YOUR_SLUG"
def main():
# Signal start
requests.get(f"{CRONOWL_URL}?start=1", timeout=10)
start_time = time.time()
try:
# Your job logic here
process_data()
duration_ms = int((time.time() - start_time) * 1000)
requests.get(f"{CRONOWL_URL}?duration={duration_ms}", timeout=10)
except Exception as e:
requests.get(f"{CRONOWL_URL}?status=failure&output={str(e)[:1000]}", timeout=10)
raise
if __name__ == "__main__":
main()const CRONOWL_URL = 'https://cronowl.com/api/ping/YOUR_SLUG';
async function runJob() {
// Signal start
await fetch(`${CRONOWL_URL}?start=1`);
const startTime = Date.now();
try {
// Your job logic here
await processData();
const duration = Date.now() - startTime;
await fetch(`${CRONOWL_URL}?duration=${duration}`);
} catch (error) {
await fetch(`${CRONOWL_URL}?status=failure&output=${encodeURIComponent(error.message)}`);
throw error;
}
}
runJob();apiVersion: batch/v1
kind: CronJob
metadata:
name: database-backup
spec:
schedule: "0 2 * * *"
jobTemplate:
spec:
template:
spec:
containers:
- name: backup
image: your-backup-image
command:
- /bin/sh
- -c
- |
curl -fsS "https://cronowl.com/api/ping/YOUR_SLUG?start=1"
START=$(date +%s%3N)
/backup.sh
EXIT_CODE=$?
END=$(date +%s%3N)
DURATION=$((END - START))
curl -fsS "https://cronowl.com/api/ping/YOUR_SLUG?duration=$DURATION&exit_code=$EXIT_CODE"
restartPolicy: OnFailurename: Scheduled Job
on:
schedule:
- cron: '0 */6 * * *' # Every 6 hours
jobs:
run:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Signal start
run: curl -fsS "https://cronowl.com/api/ping/YOUR_SLUG?start=1"
- name: Run job
id: job
run: |
START=$(date +%s%3N)
# Your job here
npm run process-data
echo "duration=$(($(date +%s%3N) - START))" >> $GITHUB_OUTPUT
- name: Signal completion
if: always()
run: |
curl -fsS "https://cronowl.com/api/ping/YOUR_SLUG?duration=${{ steps.job.outputs.duration }}"The ping endpoint accepts optional parameters to provide more context about your job.
| Parameter | Description | Example |
|---|---|---|
start=1 | Signal job start (doesn't change status) | ?start=1 |
duration | Job duration in milliseconds | ?duration=45000 |
exit_code | Exit code (0 = success, non-zero = failure) | ?exit_code=0 |
status | "success" or "failure" | ?status=failure |
output | Job output/logs (max 1-10KB based on plan) | ?output=Done |
Tip: Use exit_code to automatically mark jobs as failed when they exit with an error. CronOwl treats exit_code != 0 as a failure.
Slow Job Detection: If you set maxDuration on your check and send durationwith your ping, CronOwl will alert you when jobs take longer than expected.
| Feature | Free | Starter ($8/mo) | Pro ($19/mo) |
|---|---|---|---|
| Checks | 5 | 100 | 500 |
| HTTP Monitors | 1 | 10 | 50 |
| Status Pages | — | 1 | 5 |
| History Days | 3 | 30 | 90 |
| API Requests/min | — | 120 | 300 |
| Push & Telegram | — | ✓ | ✓ |
| Webhooks per Check | — | 3 | 10 |
| Team Members | — | — | 5 |
| Custom Branding | — | — | ✓ |
If CronOwl doesn't receive a ping within the expected time + grace period, it marks the check as "down" and sends alerts. This could mean your job failed, the network was unreachable, or the ping command wasn't executed.
CronOwl runs a status check every minute. This means you'll be alerted within 1 minute of your grace period expiring.
Yes! Click the pause button on any check in your dashboard. Paused checks won't trigger alerts even if they miss pings. Resume monitoring anytime with a single click.
exit_code is the numeric exit code from your script (0 = success, non-zero = failure).status is a string ("success" or "failure") for explicit control. If both are provided, status takes precedence.
Yes! CronOwl provides a full REST API. Use your API key to list checks, create new ones, view history, and more. See the API documentation for details.
CronOwl uses industry-standard security practices: HTTPS everywhere, encrypted data at rest, API keys are hashed before storage, and webhook URLs are validated to prevent SSRF attacks.
Need help? Contact us at support@cronowl.com