How to monitor Kubernetes CronJobs

Monitor a Kubernetes CronJob with a heartbeat: the job's command pings a URL when it succeeds, and you are alerted when a ping is late for its schedule. That catches every failure mode at once: suspended, never scheduled, crashed, stuck or silently skipped. Add kubectl get cronjob checks by hand, and Vigil's DaemonSet for failed pods.

Updated · by PuzariCloud · commands checked against current releases

What can go wrong with Kubernetes CronJobs

  • It never starts. The CronJob is suspended, the schedule is wrong, or the controller missed more than 100 schedules (for example while the controller was down with no startingDeadlineSeconds) and stopped scheduling it.
  • Skipped by concurrencyPolicy. With concurrencyPolicy: Forbid a run that is still going makes the next one be skipped, with only an event to show for it.
  • Fails after retries. The pod fails backoffLimit times (6 by default) and the Job is marked failed. Only one failed Job is kept by default (failedJobsHistoryLimit: 1), so the evidence disappears quickly.
  • Runs too long. A hung run occupies the slot for hours; activeDeadlineSeconds kills it only if you set it.
  • Wrong timezone. Without spec.timeZone (stable since Kubernetes 1.27) schedules are in the controller's timezone, usually UTC.

The signals that matter, with alert thresholds

SignalAlert whenWhy it matters
Heartbeat ping on successlate by more than the grace periodCovers every way a run can fail to happen.
Run duration (start → finish ping)> 2× the usual, or over a max run timeHung or degraded job.
Exit code reportednon-zeroThe job ran and failed.
Failed pods from Job runsanyContext for the failure.
lastSuccessfulTimeolder than one schedule interval plus graceThe same fact, checked by hand.

Thresholds are starting points. Tune them after a week of data: an alert nobody acts on should be raised or removed.

How to check Kubernetes CronJobs by hand

All CronJobs with schedule, suspend flag and last schedule
kubectl get cronjob -A
When did it last succeed?
kubectl get cronjob nightly-backup -n ops -o jsonpath='{.status.lastSuccessfulTime}'
Recent Jobs and their pods
kubectl get jobs -n ops --sort-by=.status.startTime
kubectl describe cronjob nightly-backup -n ops | tail -20
Run it now to test
kubectl create job --from=cronjob/nightly-backup manual-test -n ops
kubectl logs job/manual-test -n ops -f

How to watch Kubernetes CronJobs with Vigil

  1. In New monitor create a Heartbeat monitor, choose cron schedule and paste the CronJob's exact schedule, with the same timezone as spec.timeZone (UTC if unset). Set a grace period a bit longer than a normal run.
  2. Append the ping to the container command: /app/backup.sh && curl -fsS -m 10 --retry 3 https://puzaricloud.in/ping/<token>. Images without curl can use wget -qO- …. Only successful runs ping, so a failure is a missing ping.
  3. For duration and failure details, ping https://puzaricloud.in/ping/<token>/start first and finish with https://puzaricloud.in/ping/<token>/$? (the exit code), and set a maximum run time so a hung run alerts before the next one is due.
  4. Apply Vigil's DaemonSet from Kubernetes so failed and OOM-killed Job pods also show up as incidents with their reason.
  5. Every schedule page in the cron library includes a ready-made CronJob manifest with the ping wired in.
CronJob with start, exit-code ping and timezone
apiVersion: batch/v1
kind: CronJob
metadata:
  name: nightly-backup
spec:
  schedule: "0 2 * * *"
  timeZone: "Asia/Kolkata"
  concurrencyPolicy: Forbid
  startingDeadlineSeconds: 600
  jobTemplate:
    spec:
      backoffLimit: 2
      activeDeadlineSeconds: 3600
      template:
        spec:
          restartPolicy: Never
          containers:
          - name: backup
            image: registry.example.com/backup:1.4
            command: ["/bin/sh", "-c"]
            args:
            - |
              curl -fsS -m 10 https://puzaricloud.in/ping/$TOKEN/start
              /app/backup.sh; rc=$?
              curl -fsS -m 10 https://puzaricloud.in/ping/$TOKEN/$rc
              exit $rc
            env:
            - name: TOKEN
              valueFrom: {secretKeyRef: {name: vigil-ping, key: token}}

Every alert goes to the channels you pick: email, Slack, Telegram, Discord, Microsoft Teams, Pushover, webhooks or on-call paging. Failures are confirmed by a second check before anyone is woken up.

Kubernetes CronJobs monitoring FAQ

Why not just alert on failed Jobs?

Because the worst failures produce no Job at all: a suspended CronJob, a bad schedule, too many missed start times, or a run skipped by concurrencyPolicy. A heartbeat notices the absence of success, whatever the cause.

What grace period should I use?

Longer than the job's normal duration plus scheduling delay (image pull, pod start). For a 5-minute nightly job, 15-20 minutes is reasonable; the cron explainer suggests one per schedule.

Should the ping token be a Secret?

Yes. Anyone with the token can mark the job as successful, so keep it in a Secret, as in the example, rather than in the manifest.

Vigil by PuzariCloud

Watch Kubernetes CronJobs without stitching five tools together

Uptime and API checks, heartbeats, server and database metrics, traces, errors and logs in one place, with one incident and one alert when something breaks. Every feature is on every plan, including Free.