0 0 1 1 *
At 00:00 on day 1 of the month in January
Field by Field
| Field | Value | Matches |
|---|---|---|
| Minute | 0 | 0 |
| Hour | 0 | 0 |
| Day of month | 1 | 1 |
| Month | 1 | 1 |
| Day of week | * | every day |
Once a Year, at Midnight on 1 January
Both day and month are pinned; the day-of-week field stays *. Many schedulers accept the
alias @yearly (or @annually) for exactly this expression.
Annual Jobs Fail More Than Any Other Schedule
A year is long enough for everything the job depends on to change: credentials rotate, hostnames move, APIs deprecate, the file format changes, the person who wrote it leaves. By the time it runs, it is running in an environment nobody designed it for.
The mitigation is to run it on a schedule you can actually observe:
- Run monthly against a staging environment with the same code path.
- Make the job's date range a parameter, so you can execute last year's run on demand.
- Alert on the *absence* of a success record, not on failure — the most likely outcome is
New Year's Midnight Is Not a Quiet Time
1 January at 00:00 is a load spike for anything consumer-facing, and it is the worst
possible time to page an on-call engineer. 0 4 2 1 * — 4am on the 2nd — does the same job
in a much better hour.
Time Zones
Standard crontab evaluates in system local time. Vercel Cron, GitHub Actions, Kubernetes
CronJobs and AWS EventBridge all evaluate in UTC. If converting your local time crosses
midnight, the day-of-week field shifts too — weekdays at 5pm in UTC-07:00 is
0 0 * * 2-6, not 0 0 * * 1-5.
The Day-Field Trap
When both the day-of-month and day-of-week fields are restricted, cron matches either,
not both. 0 0 13 * 5 runs on every 13th *and* every Friday — not Friday the 13th. Leave
one of them as * unless you genuinely want the union.
Other Common Schedules
| Expression | Runs |
|---|---|
* * * * * | Every minute |
*/5 * * * * | Every 5 minutes |
*/15 * * * * | Every 15 minutes |
0 * * * * | Every hour, on the hour |
0 0 * * * | Every day at midnight |
0 9 * * * | Every day at 9am |
0 9 * * 1-5 | Weekdays at 9am |
0 9 * * 1 | Every Monday at 9am |
Making the Job Safe
- Assume double firing. DST transitions, restarts and retries all cause it. Make the job
- Assume overlap. Cron starts the next run whether or not the last one finished. Take a
- Avoid
:00. Every hourly job on the internet fires at the top of the hour; a random
- Log the start and end. A cron job that silently stops running is invisible until