Cron schedule
Cron on the first day of the month
0 0 1 * * runs at 00:00 on the first day of every month. For the first Monday or first business day, standard cron needs a date check in the command; Spring and Quartz have MON#1 and 1W.
| Expression | 0 0 1 * * |
|---|---|
| Runs | 12 times in 2027 |
| systemd OnCalendar | *-*-01 00:00:00 |
How it works
1 in the day-of-month field matches the 1st, and the month field * repeats it every month. It’s the same line as @monthly; this page is about the variations people usually need next.
The first business day depends on weekends: if the 1st is a Saturday, it’s the 3rd. Standard cron can’t express that. Quartz and Spring support 1W (“the weekday nearest the 1st, without leaving the month”), so 0 0 0 1W * ? in Quartz. In plain cron, run on days 1 to 3 and let the script decide.
The first Monday is the classic trap: it looks like 0 0 1-7 * 1 and isn’t. Each tool handles it differently, so the pitfalls below spell out what to write where.
| Field | Value | Meaning |
|---|---|---|
| Minute | 0 | minute 0 (on the hour) |
| Hour | 0 | hour 0 (12 AM) |
| Day of month | 1 | day 1 |
| Month | * | every month |
| Day of week | * | every day of the week |
Next runs
As on GitHub Actions without timezone, Vercel, Cloudflare and Kubernetes with timeZone "Etc/UTC".
| Run | Your time | UTC |
|---|---|---|
| 1 | Calculating… | |
| 2 | ||
| 3 | ||
| 4 | ||
| 5 |
Ready-to-paste versions
The same schedule for each scheduler, with what differs on each one.
crontab (Linux, macOS, BSD)
# m h dom mon dow command
0 0 1 * * /usr/local/bin/job.sh >> /var/log/job.log 2>&1- cron uses the server’s time zone. On cronie (Fedora, RHEL), a
CRON_TZ=Europe/Londonline above the entry changes the zone for the entries below it.
GitHub Actions
on:
schedule:
- cron: '0 0 1 * *'
# timezone: 'America/New_York' # optional; UTC when omitted
workflow_dispatch: {}- Runs in UTC unless you set
timezone, and only on the default branch. GitHub’s docs warn that scheduled runs can be delayed at busy times and that schedules in public repositories are disabled after 60 days without activity. - The start of every hour is GitHub’s busiest slot. If the exact minute doesn’t matter, move the
0to something like17to get picked up sooner. - With a
timezonethat observes DST, a time skipped by the spring-forward change advances to the next valid time (the docs’ example: 2:30 AM becomes 3:00 AM).
Kubernetes CronJob
apiVersion: batch/v1
kind: CronJob
metadata:
name: scheduled-job
spec:
schedule: "0 0 1 * *"
timeZone: "Etc/UTC"
concurrencyPolicy: Forbid
jobTemplate:
spec:
template:
spec:
restartPolicy: OnFailure
containers:
- name: job
image: busybox:1.36
command: ["/bin/sh", "-c", "date; echo running"]spec.timeZoneis stable since Kubernetes 1.27; without it, the kube-controller-manager’s zone applies.concurrencyPolicy: Forbidskips a run while the previous Job is still active.
Vercel Cron Jobs
{
"crons": [
{ "path": "/api/cron", "schedule": "0 0 1 * *" }
]
}- Vercel always uses UTC, doesn’t accept names like
MONorJAN, and won’t let you set both day of month and day of week. - This fits the Hobby plan, but Hobby precision is per hour: the invocation can land anywhere within the scheduled hour.
Cloudflare Workers
[triggers]
crons = ["0 0 1 * *"]- Cron Triggers run on UTC. Changes can take up to 15 minutes to propagate across Cloudflare’s network.
node-cron (Node.js)
import cron from 'node-cron';
// 6 fields: the first one (seconds) is optional
cron.schedule('0 0 0 1 * *', async () => {
await runJob();
}, { timezone: 'UTC', noOverlap: true });- The schedule lives inside the Node process: if it’s down, nothing runs, and three replicas run the job three times.
noOverlapskips a tick while the previous run is still going.
systemd timer
# /etc/systemd/system/job.timer
[Unit]
Description=Run job.service on the 1st of each month
[Timer]
OnCalendar=*-*-01 00:00:00
Persistent=true
[Install]
WantedBy=timers.target
# /etc/systemd/system/job.service
[Service]
Type=oneshot
ExecStart=/usr/local/bin/job.sh
# systemctl daemon-reload && systemctl enable --now job.timer- Check it with
systemd-analyze calendar '*-*-01 00:00:00', which prints the normalized form and the next elapse.AccuracySecdefaults to 1min, so the start can drift by up to a minute. Persistent=trueruns the job at boot if the machine was off at the scheduled time, which plain cron doesn’t do.
Spring @Scheduled and Quartz
@Scheduled(cron = "0 0 0 1 * *", zone = "UTC")
public void runJob() {
// ...
}
// Quartz CronTrigger:
CronScheduleBuilder.cronSchedule("0 0 0 1 * ?")- In Spring, the first of the 6 fields is seconds; the rest follow standard cron (0 or 7 = Sunday).
- In Quartz, one of the two day fields must be
?, and weekdays run 1 = Sunday to 7 = Saturday, which is why the example uses names.
Pitfalls
0 0 1-7 * 1 runs 10 or 11 times a month
With both day fields restricted, Vixie cron and cronie run the job when either matches: every day from the 1st to the 7th, plus every Monday. Write 0 0 1-7 * * and test the weekday in the command: [ "$(date +\%u)" = 1 ] && job.sh.
node-cron and systemd use AND, not OR
node-cron combines day of month and day of week with AND, so 0 0 1-7 * 1 really is the first Monday there. systemd works the same way: Mon *-*-01..07 00:00:00. The same string means different things on different schedulers.
Quartz won’t accept both day fields
Quartz requires ? in one of the day fields, so it rejects the OR form outright and gives you MON#1 instead: 0 0 0 ? * MON#1. Spring accepts MON#1 too.
Frequently asked questions
- What is the cron expression for the first day of the month?
- 0 0 1 * * runs at midnight on the 1st of every month. @monthly is equivalent.
- How do I run a cron job on the first Monday of the month?
- In crontab: 0 0 1-7 * * with [ "$(date +\%u)" = 1 ] && job.sh. In Spring or Quartz: MON#1 in the day-of-week field. In systemd: OnCalendar=Mon *-*-01..07 00:00:00.
- How do I run on the first business day of the month?
- Quartz and Spring support 1W in the day-of-month field. In plain cron, run on days 1-3 and have the script check whether today is the first weekday of the month.
- Does cron run on the 1st if the server was off at midnight?
- No. Plain cron doesn’t catch up. systemd timers with Persistent=true and anacron do.
Last reviewed by Arielton Oberek.