A cron job that runs 0 2 * * * on a server in UTC will run at different local times depending on where your team is and what timezone your database uses — and a cron job that runs 0 2 * * * on a server in New York will skip entirely on the night that clocks spring forward, because 2:00 AM doesn't exist that night
The previous articles on this site covered cron syntax basics, production scheduling (idempotency, monitoring), how the cron daemon works, DST and the 2:30 AM problem, and timezone desync between server and database. This article addresses cron in cloud and containerized environments — specifically how Kubernetes, Docker, and serverless platforms handle scheduled jobs differently from traditional cron, and the operational patterns that make cloud-native scheduling more reliable.
Kubernetes CronJob: what changed from traditional cron
Kubernetes CronJob (previously ScheduledJob) runs containerized jobs on a cron schedule. The key differences from traditional crontab:
Concurrency policy: traditional cron has no built-in mechanism to prevent concurrent runs — if a job takes longer than its interval, multiple instances run simultaneously. Kubernetes CronJob has three policies:
spec:
concurrencyPolicy: Forbid # Skip next run if current is still running
concurrencyPolicy: Allow # Allow concurrent runs (default, matches traditional cron)
concurrencyPolicy: Replace # Kill current run and start a new one
Forbid is the safe default for jobs that aren't idempotent — preventing the state corruption that occurs when two instances of the same job modify the same data simultaneously.
startingDeadlineSeconds: if the Kubernetes controller misses a scheduled time (control plane downtime, cluster overload), it attempts to start the job within this window:
spec:
startingDeadlineSeconds: 300 # Start within 5 minutes of scheduled time or skip
Without this, a missed schedule might trigger many catchup runs.
The successfulJobsHistoryLimit problem
Kubernetes CronJobs create Job objects (and associated Pods) for each run. Without cleanup configuration, these accumulate indefinitely — eventually consuming significant cluster resources or hitting Kubernetes API limits:
spec:
successfulJobsHistoryLimit: 3 # Keep only last 3 successful Job objects
failedJobsHistoryLimit: 1 # Keep only last 1 failed Job object
The default values (3 and 1 respectively in recent Kubernetes versions) are reasonable for most use cases. Setting to 0 deletes Job objects immediately after completion — losing the ability to inspect logs from recent runs.
The operational pattern: keep enough history to debug failures (at least 2-3 runs for failed jobs) but not so much that Jobs accumulate unbounded. For debugging, ship logs to a centralised logging system (Fluentd, Loki, Datadog) rather than relying on Pod log retention.
Serverless scheduled functions: the cold start problem
AWS EventBridge (CloudWatch Events), Google Cloud Scheduler, and Azure Scheduler trigger serverless functions on cron-like schedules. The differences from traditional cron:
Cold start latency: a serverless function that hasn't run recently may have a "cold start" delay of 100ms-2s before execution begins. For scheduled jobs, this is usually acceptable (a 2-second delay on a minute-level schedule is negligible). For latency-sensitive jobs (those that must complete before a next step begins), cold starts need to be accounted for.
Execution time limits: most serverless platforms impose execution time limits (15 minutes for AWS Lambda, 9 minutes for Google Cloud Functions, 10 minutes for Azure Functions). Long-running jobs (database maintenance, large batch exports) that exceed these limits are terminated mid-execution.
The solution for long-running scheduled jobs: break the job into multiple steps with a state machine (AWS Step Functions, Google Cloud Workflows) — the orchestrator handles state between steps, each running as a separate short-lived function.
Cron timezone specification in modern systems
Traditional cron runs in the system timezone of the server — there's no standard way to specify a timezone per job in classic crontab format.
Modern alternatives that support timezone specification:
GitHub Actions:
on:
schedule:
- cron: '0 9 * * 1' # UTC only — no timezone support
GitHub Actions always runs cron in UTC — users must convert their target local time to UTC manually.
Kubernetes CronJob (Kubernetes 1.25+):
spec:
timeZone: "Europe/London"
schedule: "0 9 * * 1" # 9 AM London time
The timeZone field was added in Kubernetes 1.25 (stable in 1.27) — enabling timezone-aware scheduling directly in the spec.
systemd Timer:
[Timer]
OnCalendar=Mon *-*-* 09:00:00 Europe/London
systemd timers natively support timezone specification.
Airflow: Python-based scheduling with full timezone support via pendulum — specifying timezone="Europe/London" on a DAG makes all schedule calculations timezone-aware.
The "missed run" problem and catchup behaviour
When a cron scheduler is offline (server reboot, cluster maintenance, deployment), scheduled runs may be missed. Different systems handle this differently:
Traditional cron (vixie cron): missed runs are not caught up — they're simply lost. If the server was down from 2 AM to 4 AM and you had a 3 AM job, it doesn't run until the next scheduled occurrence.
Kubernetes CronJob with startingDeadlineSeconds: will attempt to start missed runs within the deadline window. If multiple runs were missed and concurrencyPolicy is Forbid, only the most recent missed run is started.
Airflow: has configurable catchup behaviour per DAG. catchup=True runs all missed DAG runs from the start_date to the current time; catchup=False skips missed runs. For most production DAGs, catchup=False is safer — preventing a burst of catchup runs after a maintenance window.
How to use the Cron Explainer on sadiqbd.com
- For Kubernetes CronJob expressions: the cron format is identical to traditional cron — the explainer translates the schedule correctly; then layer in
timeZone,concurrencyPolicy, andstartingDeadlineSecondsseparately in the Kubernetes spec - For UTC-only platforms (GitHub Actions): use the explainer to verify the schedule, then convert your intended local time to UTC before pasting into the workflow file
- For DST-sensitive schedules: the previous DST article covered which times to avoid; the explainer shows when the job fires — check that the scheduled time doesn't fall in the missing hour (spring forward) or ambiguous hour (fall back) in the relevant timezone
Frequently Asked Questions
Should I use cron for database backups or is there a better approach in cloud environments? Managed database services provide their own backup scheduling that's more reliable than cron-based approaches. AWS RDS, Google Cloud SQL, and Azure SQL all provide automated backup scheduling with retention policies, point-in-time recovery, and cross-region replication — without any cron configuration. For custom backup requirements (specific dump formats, custom destinations, additional processing), cron-based approaches work but should use: idempotent scripts (won't corrupt data if run twice), output validation (check that the backup file is valid after creation), and alerting on failure (a silent backup failure is worse than no backup at all). Cloud-native scheduling (EventBridge/Cloud Scheduler) is more reliable than server-level cron for cloud workloads.
Is the Cron Explainer free? Yes — completely free, no sign-up required.
Try the Cron Explainer free at sadiqbd.com — translate any cron expression into plain English instantly.