A daily cron job runs once every 24 hours. The canonical expression:
0 0 * * *
Read field by field: minute 0, hour 0, every day-of-month, every month, every day-of-week. The job fires at midnight, every day, with no exceptions. The page above shows variants for other times of day; this companion explains what changes when you pick a different hour and what to think about for daily jobs in general.
Adjusting the hour
Replace the second field. Common variants:
| Expression | Fires at |
|---|---|
0 0 * * * | Midnight (00:00) |
0 1 * * * | 01:00 |
0 3 * * * | 03:00 (often used for backups) |
0 6 * * * | 06:00 |
0 9 * * * | 09:00 (workday-aligned reports) |
0 12 * * * | Noon |
0 17 * * * | 17:00 (end-of-business) |
0 23 * * * | 23:00 |
The hour field is 0–23 in 24-hour time. The minute field stays at 0 for “on the hour”; change it to e.g. 30 for 0:30, 1:30, etc.
Why most daily jobs run between 1 AM and 4 AM
The default convention. Reasons:
- Off-peak load. Database backups, log rotations, batch ETL — anything resource-heavy avoids competing with daytime traffic.
- Avoid midnight stampede. Every cron job written without thinking ends up at
0 0 * * *. A datacenter where 50 services all run at midnight has a 50-job thundering herd. Spreading jobs across0 1 * * *,0 2 * * *,0 3 * * *smooths the load. - Daylight saving safety. In timezones that observe DST, the 02:00 hour either repeats or is skipped twice a year. A job at
0 2 * * *may run twice or not at all on transition days. 03:00–06:00 is the “DST-safe” window in most US timezones.
Daylight saving in detail
When local time springs forward (in the US, 02:00 → 03:00 on the second Sunday in March), a 0 2 * * * job is skipped on that date — the wall clock never shows 02:00. When it falls back (03:00 → 02:00 in November), a job at 0 2 * * * may run twice depending on the cron implementation.
Workarounds:
- Pick an hour outside the DST transition window. Anything 04:00–23:00 is safe in US-style DST.
- Run on UTC. Most modern schedulers (systemd timers, Kubernetes CronJobs, cloud schedulers) accept a timezone setting; “UTC” sidesteps DST entirely.
- Make the job idempotent. A job that runs twice and produces the same end state doesn’t care about DST.
Daily vs at-most-daily
0 0 * * * fires exactly once per day (modulo DST). A common related need is “at most once per day” — re-run on retry, but don’t re-run if it already ran today. Cron alone doesn’t express this; the job has to check its own state (a touchfile, a database flag) and short-circuit if today’s run already happened.
Daily window vs daily instant
If the job has a window (e.g., “anytime between 1 AM and 5 AM”), 0 0 * * * is the wrong tool — it picks one specific minute. Implementations:
- Run on the minute, but jitter inside the job with a
sleep $((RANDOM % 1800))at the top. Spreads load across 30 minutes after the cron fires. - Use a more-capable scheduler. systemd timers support
RandomizedDelaySec. Cloud schedulers usually have a “tolerance window” or “jitter” option.
Common pitfalls
- Forgetting the timezone. A job set to “midnight” in a US East server runs at a different UTC instant than a “midnight” job on a European server. Audit the system timezone before deploying daily jobs.
- Using
0 0 * * *as “once a day” without checking if the previous run finished. A long-running daily job can overlap with the next day’s. Wrap withflockor an in-job state check. - Running daily aggregations at midnight while logs are still being written. A 23:59 log entry may not be flushed by 00:00. Run the aggregation a few minutes after the boundary (
5 0 * * *instead of0 0 * * *) to give the previous day’s data time to settle. - Treating
@dailyas identical to0 0 * * *. They are — in Vixie cron. Some schedulers don’t accept@daily. The five-field form is the portable choice. See cron expression generator for full syntax.
For other recurring schedules, see cron weekly, cron monthly, cron every hour. To read an existing expression in plain English, use the cron parser.