Uptime Kuma Cron Job Monitoring: Push URLs and Intervals
Contents
Uptime Kuma monitors cron jobs with a Push monitor: the job requests the monitor's push URL when it runs, and Uptime Kuma marks the monitor down when no push arrives within the Heartbeat Interval. The interval is counted in seconds from the last push, not read from a cron schedule, so you size it to cover the longest normal gap between runs. A job can report its own failure with status=down and a message, and send its run time in the ping parameter. Everything below was checked on October 25, 2026 against Uptime Kuma 2.5.7, the latest release, published October 23 (release), using that tag's source and the project wiki.
How an Uptime Kuma Push Monitor Works#
Create a monitor with the Push type and Uptime Kuma shows a push URL: your server's address, /api/push/, a 32-character token, and the query string ?status=up&msg=OK&ping= (EditMonitor.vue). The route accepts any HTTP method and reads three optional query parameters (api-router.js):
| Parameter | Default | What Uptime Kuma does with it |
|---|---|---|
status |
up |
up records an up beat; any other value, including down, records a down beat |
msg |
OK |
Stored with the beat and included in the notification text |
ping |
empty | Milliseconds, stored as the beat's response time and charted for up beats only (PingChart.vue); a negative value is rejected |
The token is the only credential; the wiki's API page says the push endpoint needs no other authentication (Internal API). Anyone with the URL can mark the job up, so keep it out of crontabs and repositories, and use Reset Token if it leaks. A push returns {"ok":true}, or a 404 with nothing recorded when the token is unknown, the monitor is paused, or ping is invalid.
When the Monitor Goes Down#
Uptime Kuma keeps a timer for each push monitor. When it fires, it compares the time since the last beat with the interval (monitor.js):
- If the last beat was an up push less than one Heartbeat Interval plus a one-second buffer ago, it sets the timer for the remaining time and records nothing.
- Otherwise it records "No heartbeat in the time window." With Retries at 0, the default for new monitors since v2 (migration guide), that beat is Down. With Retries at N, the monitor goes Pending, is rechecked every Heartbeat Retry Interval, and turns Down after the Nth retry (retry logic, retry interval).
- Notifications go out on the change to Down and on recovery to Up, not on Pending (monitor.js). While the monitor stays Down it records a beat every interval, and "Resend Notification if Down X times consecutively," off by default, repeats the alert every X of those beats (monitor.js).
A monitor that never receives a push fails its first check one interval after it's created, so a job that was never installed is caught too.
Retries apply to an explicit failure as well. With Retries at 0, a status=down push turns the monitor Down at once and the notification carries your msg. With Retries above 0, the push only makes it Pending (determineStatus); the Down alert comes from a later timer check and says "No heartbeat in the time window" instead of the job's message.
Push Monitors Use an Interval, Not a Cron Schedule#
The monitor page tells you to "call this URL every N seconds" (en.json), and the Heartbeat Interval is what decides when the next push is due. A 2022 request to accept a cron expression for push monitors was closed as not planned in April 2026 (#1482); a request for different intervals at different times of day is open (#3092). The only place Uptime Kuma takes a cron expression from you is a maintenance window (EditMaintenance.vue). What that means for a cron job:
- The deadline floats. It's measured from the last push, so a run that finishes two hours late moves the next deadline two hours later.
- Irregular schedules need their longest gap. A weekdays-only job needs more than 72 hours to survive the weekend, which delays a missed Tuesday alert to Thursday; cron monitoring vs uptime monitoring works through that example.
- Long intervals need 2.5.0 or later. Earlier releases, including 1.23 and 2.4, capped the interval at 24 days; 2.5.0 removed the cap (#7607).
A Cron Wrapper That Reports Success and Failure#
Set up the monitor first:
- Add a monitor of type Push, named after the job.
- Set Heartbeat Interval from the next section, keep Retries at 0, and enable at least one notification on it.
- Copy the push URL without the
?status=up&msg=OK&ping=part. If the wrapper adds parameters to the full URL, each one arrives twice,statusbecomes a list instead of the stringup, and every push is recorded as down. - Save the URL in a file owned by the job's account with mode 600, here
/etc/uptime-kuma/nightly-backup.url.
The wrapper runs the job, measures it, and pushes up or down with the exit code in the message:
#!/bin/sh
# /opt/backup-kuma.sh: run the backup, then report its result to an Uptime Kuma push monitor.
# The file holds the push URL without its query string, readable only by this account.
push_url=$(cat /etc/uptime-kuma/nightly-backup.url)
push() {
[ -n "$push_url" ] || return 0
curl --fail --silent --show-error --max-time 5 --output /dev/null \
--get "$@" "$push_url" || true
}
started=$(date +%s)
/opt/backup.sh
rc=$?
finished=$(date +%s)
elapsed_ms=$(( (finished - started) * 1000 ))
# Uptime Kuma rejects a negative ping, which a clock step could produce.
[ "$elapsed_ms" -ge 0 ] || elapsed_ms=0
if [ "$rc" -eq 0 ]; then
push --data-urlencode "status=up" \
--data-urlencode "msg=backup.sh finished" \
--data-urlencode "ping=$elapsed_ms"
else
push --data-urlencode "status=down" \
--data-urlencode "msg=backup.sh exited with code $rc" \
--data-urlencode "ping=$elapsed_ms"
fi
exit "$rc"Schedule it with 0 3 * * * /opt/backup-kuma.sh. --get with --data-urlencode builds the query string, so a message with spaces arrives intact. --max-time 5 caps each push, || true ignores a failed one, and the script exits with the backup's own code, so an unreachable Uptime Kuma can't fail or stall the job. --show-error still writes a failed push, such as the 404 for a wrong token or a paused monitor, to the job's stderr. Cron shell script best practices covers keeping exit codes honest inside the job itself.
Choosing the Heartbeat Interval for a Daily Job#
The interval must be longer than the longest normal gap between two pushes, and every hour of headroom is an hour of detection delay. For a job that pushes when it finishes, the longest gap is 24 hours, plus the difference between its longest and shortest runs, plus any start delay, plus an hour if the host's clock follows daylight saving time.
Take a backup that starts at 03:00 and takes 10 to 40 minutes:
- Host on UTC. The longest gap is 24 hours 30 minutes, from a 03:10 finish to a 03:40 finish. A 25-hour interval (90,000 seconds) leaves 30 minutes of headroom. If a run never happens, the alert comes 25 hours after the previous push, between 04:10 and 04:40 depending on how long that run took.
- Host on local time with daylight saving. On the night clocks go back, the gap between two 03:00 starts is already 25 hours, so a 25-hour interval raises a false alarm once a year. Use 26 hours (93,600 seconds).
Keep Retries at 0 and put the slack in the interval. Retries add N retry intervals to every missed-run alert, delay explicit failures, and replace your message. The grace period guide shows how to size headroom from measured run times and start delays.
A push monitor has no start signal, so a hung run shows up only when the deadline passes. A time limit inside the job turns a hang into a status=down push at a time you choose.
Restarts and Maintenance Windows Shift the Checks#
A Restart Resets the Timer#
When a push monitor starts, its first check is one full interval away (monitor.js). That happens when Uptime Kuma starts, and when you save or resume the monitor (server.js). After a restart, a missed push is caught at the later of last push plus interval and restart plus interval. With the 25-hour interval above, restarting the container at 04:00, just before a 04:10 deadline for a run that never happened, moves the alert to 05:00 the next day.
If Uptime Kuma restarts more often than the interval, for example a nightly container restart for backups, the check never comes and the monitor stays up. Issue #6866 reports this for intervals over 24 hours; a fix that checks overdue monitors at startup is proposed in PR #7067, unmerged as of October 25, 2026. Until it ships, restart less often than your longest interval, and after an upgrade, look at each long-interval monitor's last push yourself.
Pushes During Maintenance Are Recorded as Maintenance#
Maintenance windows suppress notifications and can be manual, a single window, a cron expression, or recurring by interval, weekday, or day of month, in a time zone you pick (Maintenance). A push that arrives during a window is stored as Maintenance, whether it says up or down (api-router.js). After the window, the next check treats that non-up beat as missing, so the monitor goes down unless an up push has arrived since; a project collaborator confirmed this in #6022 and suggested retries as the workaround. For a nightly job, keep windows clear of its run, or expect a "No heartbeat in the time window" alert afterward.
Self-Hosting Requirements, and Who Watches Uptime Kuma#
Uptime Kuma is MIT licensed and describes itself as "an easy-to-use self-hosted monitoring tool" (README). What you run:
- Docker: the
louislam/uptime-kuma:2image with/app/datamapped to a local directory or volume. SQLite needs POSIX file locks, so the wiki warns against NFS (install guide). Without Docker, Node.js 20.4 or later (package.json). - A database: chosen at first start, SQLite or an external MariaDB or MySQL. The full image also offers an embedded MariaDB; the
-slimtags don't (Docker tags, setup-database.js). - Backups: copying the
datadirectory is the only supported backup method in v2 (migration guide).
Uptime Kuma is one process on a host you operate. If that host, the network path from your job servers, or the route to your notification services is down, nothing alerts. Pushes sent during the outage are lost, and the restart behavior above delays the first checks afterward. Run it on different infrastructure from the jobs it watches, and have something outside it watch Uptime Kuma. Self-hosted cron monitoring covers that failure-domain question and the cost of running your own monitor more broadly.
Uptime Kuma and CronRadar Compared#
CronRadar is a hosted monitor for scheduled jobs only. Uptime Kuma facts below are from 2.5.7, checked October 25, 2026.
| Uptime Kuma 2.5.7, Push monitor | CronRadar | |
|---|---|---|
| Where it runs | Your server | Hosted; jobs report to https://cron.life |
| When a report is due | Heartbeat Interval, in seconds since the last push | Five-field cron schedule in UTC, plus a grace period (90 seconds by default, 60 minimum) |
| Signals from a job | One push per run: status, msg, ping |
Heartbeat, or start, complete, and fail with a message of up to 1,000 characters |
| Run duration | The ping number the job sends |
Recorded from a start and complete pair |
| A hung run | Seen as a missed push once the interval passes | Stuck: a start with no finish once the next expected run plus grace passes |
| Result checks | Free-text msg |
Up to five numeric or boolean outcomes checked on complete |
| Creating monitors | In the web UI; the wiki calls the Socket.io API internal and not officially supported for third-party integrations (Internal API) | A check-in that carries schedule, the dashboard, or Hangfire and Quartz.NET discovery |
| Notifications | Many notification providers (README, providers), plus Apprise (wiki) | Email, Slack, webhook, Discord, PagerDuty |
| Run history | Status changes kept 365 days by default; other beats pruned after 24 hours, except each monitor's latest 100 (clear-old-data.js, database.js) | Free: 30 days of runs, 90 days of alerts. Grow: 90 and 180 days |
| Also covers | HTTP, TCP, ping, DNS, Docker, and other checks; status pages | Scheduled jobs only: no uptime checks, status pages, logs, or on-call |
| Cost | Free software; you pay for the host and its upkeep | Free up to 10 active monitors; Grow beyond that, below |
The same backup reporting to CronRadar sends a start and a finish instead of one push. Shell scripts have no CronRadar SDK or auto-discovery, so each job reports with its own HTTP check-in:
#!/bin/sh
# /opt/backup-monitored.sh
# The API key, alone in a file only the job's account can read.
CRONRADAR_API_KEY=$(cat /etc/cronradar/api-key 2>/dev/null)
url="https://cron.life/api/v1/ping/nightly-backup"
run_id="nightly-backup-$(date +%s)-$$"
report() {
event=$1
shift
curl --fail --silent --show-error --max-time 5 --output /dev/null \
--request POST --get --user "$CRONRADAR_API_KEY:" \
--data-urlencode "schedule=0 3 * * *" \
--data-urlencode "gracePeriod=900" \
--data-urlencode "run_id=$run_id" \
"$@" "$url/$event" || true
}
report start
/opt/backup.sh
rc=$?
if [ "$rc" -eq 0 ]; then
report complete
else
report fail --data-urlencode "message=backup.sh exited with code $rc"
fi
exit "$rc"The first check-in creates the monitor from schedule and gracePeriod; editing them in the script later doesn't change an existing monitor, so change them in its dashboard settings. CronRadar reads the schedule in UTC, so on a host not set to UTC, send the UTC equivalent. The grace covers only the start: a missed run turns the monitor critical once 03:15 UTC passes, however long the previous run took. A fail alerts immediately. For an open incident CronRadar sends the first alert, at most one reminder every 24 hours, and a recovery message.
Its limits matter here too. Stuck detection works per job, not per run: the next run's start resets it, and there's no maximum-runtime setting, so a time limit inside the job is still the way to catch a hang. A monitor created by its first check-in can't flag a job that never runs; create it in the dashboard beforehand and it stays pending, then sends a never-executed alert once the first expected run plus grace passes. CronRadar can't be self-hosted, has no SMS, offers no contractual SLA, and publishes no availability figure.
Free covers up to 10 active monitors with every alert channel and no card. Grow: the first 10 active monitors stay free; each further monitor is billed monthly by schedule class, $2 Fast (gap under 5 minutes), $1 Frequent (5 minutes to under an hour), $0.50 Standard (hourly or less often), in arrears on the month's peak, with no tiers, minimum, or monitor limit, and an optional monthly budget that holds new monitors paused. Business is for custom requirements. Runs, retries, and API calls are never billed (pricing).
When Uptime Kuma Fits#
- You already run it. Uptime checks, status pages, and a few job heartbeats share one dashboard and one set of notifications.
- Your jobs run on a fixed interval, such as every 15 minutes or every hour, where an interval is the schedule.
- Nothing may leave your network. Jobs on an isolated network can push to an instance inside it.
- You want a notification service outside CronRadar's five channels, such as Telegram, listed in its README.
- Budget is zero. The software costs nothing; the host and upkeep are yours.
When a Hosted Cron Monitor Fits#
- Schedules an interval can't express: weekdays only, monthly, or fixed times several times a day, where the expected time should follow the cron expression.
- The monitor must sit outside your failure domain without you running a second one.
- You want start and finish signals, for duration, stuck runs, and checks on reported results.
- Monitors should appear from a job's first check-in or from framework discovery rather than one form per job.
Cron monitoring tools compared puts hosted options side by side. For a hosted service with the same interval model, see UptimeRobot heartbeat monitoring.
Frequently Asked Questions#
Can Uptime Kuma monitor cron jobs?#
Yes, with a Push monitor: the job requests the push URL when it finishes, and Uptime Kuma alerts when no push arrives within the Heartbeat Interval. It fits jobs on a fixed interval best; for daily and irregular schedules the interval must cover the longest normal gap, which delays detection.
How does an Uptime Kuma push monitor work?#
The job calls a URL that carries a secret token and optional status, msg, and ping values. Uptime Kuma doesn't poll the job; a timer checks whether the last up push is older than the Heartbeat Interval, and if so the monitor goes Pending or Down depending on Retries. Notifications go out on the change to Down and on recovery.
Can Uptime Kuma use a cron schedule for push monitors?#
No. In 2.5.7 a push monitor takes an interval in seconds, and a feature request for cron expressions was closed as not planned. Cron expressions work only for maintenance windows, which suppress notifications rather than define when a push is due.
How do I report a failed cron job to Uptime Kuma?#
Push with status=down and a URL-encoded msg describing the failure, then exit with the job's own code. With Retries at 0 that push alerts immediately with your message; with retries, it only sets the monitor to Pending.
What happens if the Uptime Kuma server is down?#
Nothing alerts. Pushes during the outage fail, and when Uptime Kuma comes back, each push monitor's first check waits one full interval, so a run missed around the outage can be reported up to one interval late. Watch the Uptime Kuma host from somewhere else.
Put this guide to work
Know when the next run is missing
Connect one scheduled job, confirm a real completion, then verify the alert destination. Keep your existing scheduler in charge of running the work.