Cron expression for daily at midnight
The cron expression 0 0 * * * runs a job at midnight every day — exactly when the wall clock reads 00:00. Standard 5-field syntax works in Linux crontab, Kubernetes CronJob and GitHub Actions; AWS EventBridge needs the 6-field form cron(0 0 * * ? *). The single biggest gotcha: most schedulers default to UTC, not your local timezone.
Quick reference
| Platform | Expression | Default timezone |
|---|---|---|
| Unix / Linux crontab | 0 0 * * * or @daily | System local |
| Kubernetes CronJob (≥ 1.25) | 0 0 * * * + spec.timeZone | UTC if timeZone unset |
| Kubernetes CronJob (< 1.25) | 0 0 * * * | Controller’s local (usually UTC) |
| GitHub Actions | 0 0 * * * | UTC always |
| AWS EventBridge Rules | cron(0 0 * * ? *) | UTC always |
| AWS EventBridge Scheduler | cron(0 0 * * ? *) + ScheduleExpressionTimezone | UTC if timezone unset |
Which timezone is “midnight”?
This is the question that catches everyone at least once. The cron expression 0 0 * * * is a wall-clock pattern: fire when the displayed minute is 0 and the displayed hour is 0. But which clock?
Each platform has a default:
- Linux crontab uses the system’s
/etc/localtime— usually whatever you set when installing the OS. Rundateon the host to see what cron sees. - Kubernetes < 1.25 uses the kube-controller-manager pod’s local time. On managed Kubernetes (EKS, GKE, AKS) this is UTC. Self-hosted clusters can be anything.
- Kubernetes ≥ 1.25 accepts a
spec.timeZonefield. If unset, falls back to controller local. Always set it explicitly. - GitHub Actions is hardcoded UTC. There is no override; you must convert.
- AWS EventBridge Rules is hardcoded UTC. EventBridge Scheduler (newer service) accepts
ScheduleExpressionTimezone.
If you wrote 0 0 * * * thinking “midnight in my timezone” and the scheduler is on UTC, your job runs at 4 PM the previous day for someone on the US West Coast. The fix is either to convert your time to UTC, or to set the timezone explicitly.
Variations
| Schedule | Expression |
|---|---|
| Daily at 9 AM Pacific (= 17:00 UTC) | 0 17 * * * |
| Daily at 9 AM Indian Standard Time (= 03:30 UTC) | 30 3 * * * |
| Just before midnight (23:59) | 59 23 * * * |
| 5 minutes after midnight | 5 0 * * * |
| Daily at midnight in AWS | cron(0 0 * * ? *) |
| Daily at midnight Pacific in EventBridge Scheduler | cron(0 0 * * ? *) + ScheduleExpressionTimezone: 'America/Los_Angeles' |
How do I use it on each platform?
Linux crontab:
0 0 * * * /usr/local/bin/nightly-cleanup
Or with the shortcut:
@daily /usr/local/bin/nightly-cleanup
Kubernetes CronJob (1.25+ with explicit timezone):
apiVersion: batch/v1
kind: CronJob
metadata:
name: nightly-cleanup
spec:
schedule: "0 0 * * *"
timeZone: "America/Los_Angeles" # critical — runs at local midnight
successfulJobsHistoryLimit: 3
jobTemplate:
spec:
template:
spec:
containers:
- name: cleanup
image: my-org/cleanup:1.0
restartPolicy: OnFailure
AWS EventBridge Scheduler (with timezone):
Type: AWS::Scheduler::Schedule
Properties:
Name: nightly-cleanup
ScheduleExpression: 'cron(0 0 * * ? *)'
ScheduleExpressionTimezone: 'America/Los_Angeles'
FlexibleTimeWindow:
Mode: 'OFF'
Target:
Arn: !GetAtt CleanupFunction.Arn
RoleArn: !GetAtt SchedulerRole.Arn
GitHub Actions (UTC only):
on:
schedule:
- cron: '0 0 * * *' # this is midnight UTC, not your local midnight
Common mistakes
Assuming 0 0 * * * runs at your midnight. It runs at the scheduler’s midnight. Always set the timezone explicitly when the platform supports it; otherwise convert to UTC.
Daylight saving silently shifts the run. A timezone like America/Los_Angeles follows DST. A job set to “midnight Pacific” is at 08:00 UTC half the year and 07:00 UTC the other half. If your downstream system expects a fixed UTC time, use UTC directly instead of a timezone.
Confusing 0 0 * * * with * * * * * or other typos. 0 0 * * * is daily; * * * * * is every minute (1440 runs/day). Easy to mis-type when copying.
The timezone story is the biggest gotcha here — for the full set of platform-specific knobs (Kubernetes timeZone post-1.25, EventBridge Scheduler ScheduleExpressionTimezone), see the Kubernetes CronJob schedule generator and the AWS EventBridge cron generator.
For other schedules see common cron schedules, or build a custom expression with the Cron Expression Builder.
Frequently asked questions
- Does
0 0 * * *run at local midnight or UTC midnight? - Depends on the platform. Linux crontab uses the system's local timezone. Kubernetes CronJob before 1.25 uses the kube-controller-manager's timezone (usually UTC); 1.25+ accepts a
spec.timeZonefield. AWS EventBridge Rules are always UTC; EventBridge Scheduler acceptsScheduleExpressionTimezone. GitHub Actions runs in UTC, no timezone option. Always verify before relying on local midnight. - Why did my "daily at midnight" job run at 5 PM yesterday?
- Timezone mismatch. You wrote
0 0 * * *thinking "midnight Pacific" but the scheduler interpreted it as UTC. UTC midnight = 5 PM PST = 4 PM PDT the previous day. Either set the timezone explicitly (K8s 1.25+timeZone, EventBridge SchedulerScheduleExpressionTimezone) or convert to UTC: 9 AM PST = 17:00 UTC, so0 17 * * *. - What is
@dailyand is it the same as@midnight? - In Vixie cron,
@dailyand@midnightare aliases — both expand to0 0 * * *. They work in Linux crontab, anacron, Kubernetes CronJob, GitHub Actions. Neither works in AWS EventBridge. Stick with the explicit form when targeting AWS.
Need a different schedule?
Build cron expressions for Unix, Kubernetes, AWS EventBridge and Quartz — with a human-readable description and the next 5 run times.
Open the Cron Expression Builder →Related
Cron expression for every 15 minutes
The cron expression */15 * * * * runs every 15 minutes — at :00, :15, :30, :45 — across Linux, Kubernetes, GitHub Actions and AWS.
Cron expression for every 2 hours
The cron expression 0 */2 * * * runs every 2 hours on the hour — at 00:00, 02:00, 04:00 etc. Step syntax (*/N) works in any field.
Cron expression for every 3 hours
The cron expression 0 */3 * * * runs every 3 hours on the hour — 8 firings per day with no day-boundary gap. Linux/Kubernetes/GitHub Actions; cron(0 */3 * * ? *) in AWS.
Cron expression for every 30 minutes
The cron expression */30 * * * * runs every 30 minutes — at :00 and :30 of every hour. Same in Linux, Kubernetes and GitHub Actions; cron(*/30 * * * ? *) in AWS.
Cron expression for every 30 seconds
Cron cannot run every 30 seconds — it has no seconds field, only minutes. Use setInterval, KEDA cron-trigger or a long-running scheduler instead.
Cron expression for every 4 hours
The cron expression 0 */4 * * * runs every 4 hours on the hour — at 00:00, 04:00, 08:00, 12:00, 16:00 and 20:00. Six runs per day across Linux, Kubernetes and GitHub Actions; cron(0 */4 * * ? *) in AWS.