GUIDE · KUBERNETES · FREE

Monitor Kubernetes CronJobs — without an operator

One curl line inside the job command reports success or failure. Absence detection covers everything the job can't report itself — a suspended CronJob, a missed schedule, a pod that never started. No agent, no operator, nothing installed in the cluster. Free, no monitor cap.

CronJob failures live where nobody looks

Kubernetes retries, backs off, records an event — and tells no human anything.

Failure mode 01

Failures buried in kubectl

A failed Job leaves a red pod and an event — visible only if someone happens to run kubectl get jobs in the right namespace. When backoffLimit is exhausted, the retries stop and so does the evidence trail.

Failure mode 02

The job that's never scheduled

suspend: true left over from a migration, a missed startingDeadlineSeconds, a node pool scaled to zero — the CronJob doesn't fail, it just never happens. Nothing inside the job can report that.

Failure mode 03

The heavyweight alternative

The "proper" fix is kube-state-metrics + Prometheus + Alertmanager and a PromQL alert per job — a full observability stack to learn whether one nightly backup ran.

The dead-man's-switch approach needs none of it: the job pings when it completes, and LastPing alerts when the ping doesn't arrive — whatever the reason. The failure modes the job can't see are covered by the same silence.


Setup: one line in the job command

A copy-paste manifest — adapt the image and command to your workload.

Create a heartbeat monitor

In the console, create a monitor with the same schedule as your CronJob's spec.schedule (cron expressions with an IANA timezone are supported) plus a grace window. Copy the ping URL:

https://ping.lastping.dev/<your-monitor-id>

Add the ping to the CronJob manifest

Success pings the monitor URL; failure pings /fail (so you're alerted immediately) and exits non-zero, which keeps Kubernetes' own retry and backoff semantics intact:

apiVersion: batch/v1 kind: CronJob metadata: name: nightly-backup spec: schedule: "0 3 * * *" jobTemplate: spec: backoffLimit: 1 template: spec: restartPolicy: Never containers: - name: backup image: your-registry/backup:latest env: - name: LASTPING_URL value: "https://ping.lastping.dev/<your-monitor-id>" command: - /bin/sh - -c - | if /app/backup.sh; then curl -fsS -m 10 --retry 3 -o /dev/null "$LASTPING_URL" else curl -fsS -m 10 --retry 3 -o /dev/null "$LASTPING_URL/fail" exit 1 fi

Minimal images: use busybox wget

Alpine and busybox-based images ship wget even when curl is absent — the ping accepts GET, HEAD, or POST, so either client works:

wget -q -T 10 -O /dev/null "$LASTPING_URL"

busybox wget has no retry flag; if your image has curl, prefer it with --retry 3.

Optional: keep the ping URL in a Secret

The ping URL is a capability URL — anyone who has it can ping your monitor. Treat it like a credential:

kubectl create secret generic lastping \ --from-literal=ping-url="https://ping.lastping.dev/<your-monitor-id>"
# in the container spec, replace the env block with: env: - name: LASTPING_URL valueFrom: secretKeyRef: name: lastping key: ping-url

What this catches

The job reports what it can; the silence reports the rest.

  • The workload failed — the /fail ping opens an incident immediately, and the non-zero exit still lets Kubernetes retry per backoffLimit.
  • The job never ran — suspended CronJob, missed startingDeadlineSeconds, image pull failure, node pool at zero, controller trouble. No ping in the expected window → incident.
  • The pod never completed — eviction, OOMKill mid-run, a hung process. The success ping never fires; the grace window raises the alert.
  • Recovery — the next successful run closes the incident and sends a recovery notice automatically.
  • Flap protection — a run that fails once and recovers within the damping window doesn't page you.

Running the same job outside the cluster too? The pattern is identical for plain cron and systemd timers. For CI pipelines, see monitoring GitHub Actions — or browse all guides.


Questions people ask

Front-loaded answers — the most important fact first.

  • How do I get alerted when a Kubernetes CronJob fails?

    Add one curl line to the job command: run the workload, then on success curl the monitor URL, and on failure curl the /fail endpoint and exit non-zero. LastPing opens an incident and alerts you by email, Telegram, Slack, Discord, or webhook — and the non-zero exit keeps Kubernetes' retry and backoff semantics intact.

  • Do I need an operator, agent, or Prometheus stack?

    No. Nothing is installed in the cluster — the job itself makes one outbound HTTP request (GET, HEAD, or POST) when it finishes. curl or busybox wget both work, so it runs in minimal images.

  • What about CronJobs that never run at all?

    That's exactly what absence detection covers. LastPing expects the ping on the schedule you set plus a grace window. A CronJob that is suspended, misses its startingDeadlineSeconds, can't pull its image, or lives in a cluster whose controller stopped scheduling produces no ping — and the silence itself opens an incident.

  • Is this free?

    Yes. LastPing is free and fully hosted with no monitor cap and no paid tier. See how it compares to Healthchecks.io and Cronitor.

FREE · FULLY HOSTED · NO PAID TIER

kubectl won't call you at 3 a.m. This will.

One line in the manifest now beats spelunking through events later. First alert wired up in minutes — and LastPing monitors itself the same way.