Skip to main content
governance · kubernetes

CronJobs paused with spec.suspend and never resumed

resource types
1
rule IDs covered
3
severity
low

What does ZopNight detect here?

ZopNight flags a Kubernetes CronJob on EKS, GKE or AKS whose `spec.suspend` is `true` once the CronJob is at least 48 hours old. A suspended CronJob keeps its schedule but starts no Jobs, so a pause meant for one maintenance window can quietly switch off a backup or report for months.

Signal and threshold

How ZopNight evaluates CronJobs paused with spec.suspend and never resumed.
Field Value
Rule IDsRC-1733 · RC-1833 · RC-1933
Categorygovernance
Severitylow
Metricspec.suspend
Thresholdtrue, CronJob at least 48h old
Evaluation window48h
SourceZopNight
Permissions usedlist cronjobs.batch

Suspension stops the work, not the schedule

Setting .spec.suspend to true tells the CronJob controller to stop creating Jobs. The CronJob documentation describes subsequent executions as remaining scheduled while the controller declines to start them, and notes that Jobs already running are not affected. The field defaults to false.

Suspension is the right tool during an incident or a migration. The trouble is that nothing reminds anyone to undo it. kubectl get cronjobs still lists the object with its schedule, so a CronJob that has not run since the spring can look perfectly healthy at a glance.

Listing suspended CronJobs

Terminal window
kubectl get cronjobs -A -o json | jq -r '
.items[]
| select(.spec.suspend == true)
| "\(.metadata.namespace)/\(.metadata.name) schedule=\(.spec.schedule) lastRun=\(.status.lastScheduleTime // "never")"'

The lastRun column tells you how long each one has actually been idle.

How the 48-hour gate works

The rule fires when two things are true: the CronJob reports suspend: true, and the CronJob object was created at least 48 hours ago. The age comes from the CronJob’s creation timestamp, which keeps a freshly created, deliberately paused CronJob out of the results. It is not a measure of how long the suspension has lasted, so a long-lived CronJob suspended an hour ago for a deploy will appear on the next evaluation. When the creation timestamp is unavailable, the rule does not raise a finding.

What falls outside it

A CronJob whose suspend field was not captured is skipped. Unsuspended CronJobs that have never run are the subject of a different check, CronJob never scheduled, and the two never fire on the same object. The rule has no view of intent, so it cannot tell a forgotten pause from one that is documented.

No saving, but a gap in coverage

This governance recommendation has no savings figure: a suspended CronJob uses no compute. The exposure is the work it is not doing and inventory clutter that hides it.

Resuming or retiring the CronJob

  1. Ask the owning team whether the job’s work still matters.
  2. Before resuming, check startingDeadlineSeconds. Kubernetes counts runs skipped while suspended as missed.
  3. Resume with kubectl patch cronjob <name> -p '{"spec":{"suspend":false}}'.
  4. If it is a paused-on-purpose runbook job, annotate it with the reason and an owner so the next review can see why.
  5. If the work is gone, delete it with kubectl delete cronjob <name>.

See it fire on your bill.

Connect an account read-only. The first findings land in minutes.

472 rule families across 353 resource types on 22 platforms. Every threshold, metric, and IAM action is documented on these pages before you grant anything.

472 rule families documented
353 resource types covered
read-only default access level
Multi-cloud automation· Production-ready in 30 min· SOC 2 · ISO 27001· 20–60% off the bill, first month· 4 platforms · 1 console· Multi-cloud automation· Production-ready in 30 min· SOC 2 · ISO 27001· 20–60% off the bill, first month· 4 platforms · 1 console·