kubernetes k8s containers

Kubernetes CronJob vs Unix Cron: What's Different and What to Watch For

by Sinthuyan  · April 15, 2026

Kubernetes CronJob is conceptually similar to Unix cron — you give it a schedule expression and it runs a container on that schedule. But the implementation differs in important ways that affect reliability, especially in large clusters or during maintenance windows.

The Schedule Field

The spec.schedule field accepts standard 5-field cron syntax: schedule: "0 2 * * *". This is the same format as Unix cron — minute, hour, DOM, month, DOW. No seconds field (unlike Quartz), no hash symbol (unlike Jenkins).

As of Kubernetes 1.27, there is also a spec.timeZone field: timeZone: "America/New_York". This is a significant improvement over raw Unix cron, which requires you to set CRON_TZ in the crontab and manage it per-host.

concurrencyPolicy: The Most Commonly Misconfigured Field

The spec.concurrencyPolicy field controls what happens if a scheduled run starts while a previous run is still executing:

  • Allow (default) — start a new Job even if the previous one hasn't finished. Two or more instances can run simultaneously.
  • Forbid — skip the scheduled run if the previous is still running. The new run is abandoned.
  • Replace — cancel the currently running Job and start a new one.

The default Allow policy is almost always wrong for batch jobs. If a database export takes 90 minutes and runs hourly, you'll accumulate stacked executions that compete for resources. Production workloads should default to Forbid unless you've explicitly designed the job to be safely concurrent.

startingDeadlineSeconds: Handling Cluster Downtime

If the Kubernetes controller-manager is down during a scheduled run (planned maintenance, cluster upgrade, node failure), the CronJob misses that execution. When the controller comes back up, Kubernetes checks how many runs were missed. If more than 100 executions were missed in the startingDeadlineSeconds window, Kubernetes stops scheduling the CronJob entirely.

Set startingDeadlineSeconds: 300 (5 minutes) for jobs where a delayed start is acceptable. This tells Kubernetes: "if we come back online within 5 minutes of the scheduled time, trigger the missed run; otherwise, skip it." Without this field, Kubernetes may fire a burst of catch-up runs when the cluster recovers.

History Limits: Clean Up After Yourself

By default, Kubernetes retains completed Job objects (and their associated Pod logs) indefinitely. On a cluster running dozens of CronJobs with frequent schedules, this creates thousands of completed Pod objects that slow down kubectl get pods and consume etcd storage.

Set these fields explicitly:

  • successfulJobsHistoryLimit: 3 — keep the last 3 successful runs
  • failedJobsHistoryLimit: 1 — keep the last failed run for debugging

Suspending Without Deleting

Setting spec.suspend: true pauses scheduling without deleting the CronJob. Use this during maintenance windows or while debugging a failing job. Restore scheduling by setting it back to false. This is cleaner than deleting and recreating the CronJob, which loses history.

Scheduling Jitter

Kubernetes CronJob scheduling is not instant. The controller-manager evaluates schedules on a polling interval and creates Job objects asynchronously. In practice, scheduled jobs start 1–30 seconds after the nominal scheduled time. This is usually acceptable, but not appropriate for jobs that must run at a precise wall-clock time (financial cutoffs, coordinated multi-cluster pipelines).

Unix Cron vs CronJob: The Key Trade-off

Unix cron is a process on a specific node — if that node goes down, the cron daemon stops. Kubernetes CronJob is managed by the control plane and survives individual node failures. The trade-off is that CronJob adds complexity (RBAC, service accounts, resource limits) and jitter. For simple, node-local tasks (log rotation, disk cleanup), a DaemonSet running cron inside a container is often simpler than a CronJob.

Related Guides