Cron expression for every quarter
The cron expression 0 0 1 */3 *
runs at 12:00 AM, on day 1 of the month, every 3 months. The restricted fields are minute 0, hour 0, day of month 1, and month */3; the remaining asterisks match anything. That works out to 4 runs per year.
0 0 1 */3 * Field breakdown
| Field | Value | Meaning |
|---|---|---|
| Minute | 0 | The top of the hour (:00) |
| Hour | 0 | 00:00 (midnight) |
| Day of month | 1 | Day 1 of the month |
| Month | */3 | Every 3 months, counted from 1: 1, 4, 7, 10 |
| Day of week | * | Every day of the week (0–6) |
Next runs
- —
- —
- —
- —
- —
Run times are calculated in your browser timezone. The scheduler that executes the job uses its own — always UTC on Vercel Cron, UTC by default on GitHub Actions, the host clock on crontab.
How this schedule works
Cron evaluates the five fields of 0 0 1 */3 * — minute, hour, day of month, month, day of week — against the clock once a minute and starts the job on any minute where all five match, which cronstrue reads as "At 12:00 AM, on day 1 of the month, every 3 months": 4 runs per year. Day of month is pinned to 1 and the month field selects January, April, July, and October, while day of week stays an asterisk — so the run lands on whatever weekday that date happens to be, and never drifts to a neighbouring day.
When to use every quarter
Quarterly runs cover financial and usage reports, key rotation on a ninety-day rhythm, and recomputing retention cohorts once a quarter closes. Four runs a year is few enough that each one is effectively a scheduled event rather than routine automation. Settle two things up front: whether your quarters are calendar quarters, since the step form fires in January, April, July and October and a fiscal year starting elsewhere needs an explicit month list; and whether the job can be re-run by hand if the one attempt it gets in three months fails.
Common mistakes with every quarter
A step in the month field counts from month 1, so this schedule is locked to January, April, July and October. If your fiscal quarters start in February the expression is wrong by a month, and it will stay wrong for a year before anyone reconciles the numbers — write the months out as a list instead. The larger risk is rehearsal. A job that runs four times a year is almost never exercised: credentials expire between runs, upstream schemas change, and the first anyone hears of it is the quarter-end when the report is due. Make it runnable on demand, and run it deliberately.
Platform snippets
The same expression, ready to paste into the four schedulers that read five-field cron. The notes below each snippet are the ones that follow from this schedule; the cron expression generator lists every caveat for every platform.
GitHub Actions
name: Scheduled workflow
on:
schedule:
- cron: '0 0 1 */3 *'
workflow_dispatch:
jobs:
scheduled:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run job
run: echo "Running scheduled job" - • Workflows scheduled at the top of the hour may be delayed during high-load periods. Consider offsetting the minute field (e.g. "5 0 1 */3 *") for more reliable timing.
Kubernetes CronJob
apiVersion: batch/v1
kind: CronJob
metadata:
name: my-cron-job
spec:
schedule: "0 0 1 */3 *"
jobTemplate:
spec:
template:
spec:
containers:
- name: job
image: my-image:latest
command: ["/bin/sh", "-c", "echo Running"]
restartPolicy: OnFailure - • Do NOT use CRON_TZ or TZ inside the schedule string. Kubernetes 1.29+ rejects this at creation with a validation error; earlier versions issue a warning. Existing CronJobs using TZ/CRON_TZ will continue to warn on update. Use the timeZone field instead.
Vercel Cron
{
"$schema": "https://openapi.vercel.sh/vercel.json",
"crons": [
{
"path": "/api/cron",
"schedule": "0 0 1 */3 *"
}
]
} - • Hobby plan: invocations occur within the specified hour (e.g. "0 8 * * *" runs between 08:00:00 and 08:59:59).
crontab
# Edit your crontab: crontab -e
0 0 1 */3 * /path/to/command
# With logging:
0 0 1 */3 * /path/to/command >> /var/log/cron.log 2>&1 Variations
-
0 0 1 1,4,7,10 *At 12:00 AM, on day 1 of the month, only in January, April, July, and October The identical schedule with the months named outright. Four values is short enough that the list is the more honest form. -
0 0 1 */6 *At 12:00 AM, on day 1 of the month, every 6 months January and July — twice a year rather than four times. The half-yearly counterpart, useful for slower key rotations. -
0 0 1 2,5,8,11 *At 12:00 AM, on day 1 of the month, only in February, May, August, and November February, May, August, November. The only way to express a fiscal year that does not begin in January: a step cannot be offset. -
0 9 1 */3 *At 09:00 AM, on day 1 of the month, every 3 months The same quarters at 09:00. A report that four people are waiting for should not run while all four are asleep.
Frequently asked questions
How do I read the cron expression 0 0 1 */3 *?
Read the five space-separated fields left to right as minute, hour, day of month, month, and day of week. In 0 0 1 */3 * the minute is 0, hour is 0, day of month is 1, month is */3, day of week is *. Of the operators cron defines, this expression uses only the ones that matter here: an asterisk matches every value in its field and a slash introduces a step counted from the start of the field. Put together, cronstrue reads the whole thing as "At 12:00 AM, on day 1 of the month, every 3 months", and a scheduler that checks all five fields once a minute will start the job 4 runs per year.
What timezone does 0 0 1 */3 * run in?
The expression carries no timezone of its own: it names hour 0, and the scheduler decides which hour 0 that is. Read as UTC, the run lands at 00:00 — 19:00 the previous day in New York, 00:00 in London, and 09:00 in Tokyo on a January date, with daylight saving moving the first two later in the year, so the same UTC instant is 20:00 the previous day in New York in July. That offset crosses a calendar day, and this schedule is pinned to the 1st of the month, so the date moves with it: the run starts at 00:00 UTC on the 1st, which is 19:00 on the last day of the previous month in New York. A job that archives "the period that just closed" is therefore running while that period is, locally, still open. Set the zone rather than converting by hand where you can: GitHub Actions defaults to UTC but takes an optional timezone key beside cron, and a Kubernetes CronJob has spec.timeZone, stable since 1.27. Where you cannot, convert deliberately: Vercel Cron is UTC-only, and a Linux crontab follows the host clock unless CRON_TZ overrides it.
Which months does 0 0 1 */3 * actually run in?
The month step counts from month 1, so the schedule fires in January, April, July, and October — the calendar quarters. It never drifts, because the step is anchored to the start of the field rather than to the previous run. A fiscal year that starts in February needs an explicit list instead: 0 0 1 2,5,8,11 * moves every run one month later. Note that the day-of-month field is set to 1 and the day-of-week field is left as an asterisk, so the schedule fires on whatever weekday the 1st happens to be.
Related cron schedules
-
0 0 1 * *Every month -
0 0 1 1 *Every year -
* * * * *Every minute -
*/5 * * * *Every 5 minutes -
*/10 * * * *Every 10 minutes -
*/15 * * * *Every 15 minutes
Build a custom schedule in the cron expression generator, or browse every epochkit tool.