Run a cron job on the first Monday of the month

0 0 9 ? * MON#1 Check it below, then take the line for any of 17 schedulers.

Click to edit · paste a crontab line · hover a field for its values

At 09:00 on the first Monday of the month.

 

Nothing to watch out for on Quartz.

Trigger
TriggerBuilder.newTrigger()
    .withSchedule(CronScheduleBuilder.cronSchedule("0 0 9 ? * MON#1")
        .inTimeZone(TimeZone.getTimeZone("UTC")))
    .build();

Details

Upcoming runs
Calendar 12 days a year
Between two dates
Daylight saving

When things go wrong Server off: Fires once when it can (smart misfire policy), if the job store survived
Server was off
Fires once when it can (smart misfire policy), if the job store survived. The default RAM store doesn't.
Last run still going
Runs concurrently, unless the job has @DisallowConcurrentExecution.
A run fails
Logged. The trigger keeps its schedule. No retry.
Time zone
The JVM's zone, or inTimeZone() on the trigger.
Precision
1 second.

Sources: github.com,github.com

Other schedulers As written in 1 of 16
  • ↻ Debian / Ubuntu cron 0 9 * * 1 [ "$(date +\%d)" -le 7 ] && … Debian / Ubuntu cron takes 5 fields: minute hour day month weekday. vs RunWisp
  • ↻ cronie 0 9 * * 1 [ "$(date +\%d)" -le 7 ] && … cronie takes 5 fields: minute hour day month weekday. vs RunWisp
  • ↻ BusyBox crond 0 9 * * 1 [ "$(date +\%d)" -le 7 ] && … BusyBox crond takes 5 fields: minute hour day month weekday.
  • ↻ systemd timer Mon *-*-01..07 09:00:00 UTC systemd timer uses OnCalendar=, not cron lines. vs RunWisp
  • ↻ RunWisp 0 9 * * 1 [ "$(date +%d)" -le 7 ] && … RunWisp takes 5 fields, or 6 with seconds first.
  • ✗ Kubernetes CronJob Kubernetes CronJob takes 5 fields: minute hour day month weekday. Kubernetes CronJob can't run this: it needs a day of the month and a weekday at once.
  • ✗ Ofelia Ofelia doesn't take # for the nth weekday. Ofelia can't run this: it needs a day of the month and a weekday at once. vs RunWisp
  • ✗ Dagu Dagu takes 5 fields: minute hour day month weekday. Dagu can't run this: it needs a day of the month and a weekday at once. vs RunWisp
  • ↻ PM2 cron_restart 0 9 1-7 * 1 PM2 cron_restart takes 5 fields, or 6 with seconds first. vs RunWisp
  • ✗ Jobber Jobber doesn't take ?. Jobber can't run this: it needs a day of the month and a weekday at once. vs RunWisp
  • ✗ GitHub Actions GitHub Actions takes 5 fields: minute hour day month weekday. GitHub Actions can't run this: it needs a day of the month and a weekday at once.
  • ↻ GitLab CI schedule 0 9 * * 1#1 GitLab CI schedule takes 5 fields: minute hour day month weekday.
  • ↻ AWS EventBridge Scheduler cron(0 9 ? * 2#1 *) AWS EventBridge Scheduler takes cron() with 6 fields, a year last, and ? in one day field.
  • ✗ Google Cloud Scheduler Google Cloud Scheduler takes 5 fields: minute hour day month weekday. Google Cloud Scheduler can't run this: it needs a day of the month and a weekday at once.
  • ↻ Jenkins 0 9 1-7 * 1 Jenkins takes 5 fields: minute hour day month weekday.
  • ✓ Spring @Scheduled 0 0 9 ? * MON#1

✓ works as written · ↻ different syntax · ≠ runs at other times · ? not documented · ✗ can't

Schedulers

On the first Monday of the month, in every scheduler

Works as written in 2 of 17. Times are UTC; the checker above takes any zone.

Runs in 2027
12
Days with a run
12
Longest gap
35 days

Don't write 0 9 1-7 * 1 in a crontab. With both day fields set, cron runs on days 1 to 7 and on every Monday, about ten days a month.

SchedulerLineNotes
Debian / Ubuntu cron ↻ 0 9 * * 1 [ "$(date +\%d)" -le 7 ] && command Debian / Ubuntu cron takes 5 fields: minute hour day month weekday.
cronie ↻ 0 9 * * 1 [ "$(date +\%d)" -le 7 ] && command cronie takes 5 fields: minute hour day month weekday.
BusyBox crond ↻ 0 9 * * 1 [ "$(date +\%d)" -le 7 ] && command BusyBox crond takes 5 fields: minute hour day month weekday.
systemd timer ↻ OnCalendar=Mon *-*-01..07 09:00:00 UTC systemd timer uses OnCalendar=, not cron lines.
RunWisp ↻ 0 9 * * 1 [ "$(date +%d)" -le 7 ] && command RunWisp takes 5 fields, or 6 with seconds first.
Kubernetes CronJob ✗ Kubernetes CronJob takes 5 fields: minute hour day month weekday. Kubernetes CronJob can't run this: it needs a day of the month and a weekday at once.
Ofelia ✗ Ofelia doesn't take # for the nth weekday. Ofelia can't run this: it needs a day of the month and a weekday at once.
Dagu ✗ Dagu takes 5 fields: minute hour day month weekday. Dagu can't run this: it needs a day of the month and a weekday at once.
PM2 cron_restart ↻ 0 9 1-7 * 1 PM2 cron_restart takes 5 fields, or 6 with seconds first.
Jobber ✗ Jobber doesn't take ?. Jobber can't run this: it needs a day of the month and a weekday at once.
GitHub Actions ✗ GitHub Actions takes 5 fields: minute hour day month weekday. GitHub Actions can't run this: it needs a day of the month and a weekday at once.
GitLab CI schedule ↻ 0 9 * * 1#1 GitLab CI schedule takes 5 fields: minute hour day month weekday.
AWS EventBridge Scheduler ↻ cron(0 9 ? * 2#1 *) AWS EventBridge Scheduler takes cron() with 6 fields, a year last, and ? in one day field.
Google Cloud Scheduler ✗ Google Cloud Scheduler takes 5 fields: minute hour day month weekday. Google Cloud Scheduler can't run this: it needs a day of the month and a weekday at once.
Jenkins ↻ 0 9 1-7 * 1 Jenkins takes 5 fields: minute hour day month weekday.
Quartz ✓ 0 0 9 ? * MON#1 Works as written.
Spring @Scheduled ✓ 0 0 9 ? * MON#1 Works as written.

✓ works as written · ↻ different syntax · ≠ runs at other times · ? not documented · ✗ can't

RunWisp

Cron runs the line. Nobody tells you how it went.

RunWisp is an open-source cron replacement that ships as a single binary. It reads the same cron line and keeps a record of every run: output, exit code, duration.

crontab
# crontab -e  (runs on the server's clock: UTC)
0 9 * * 1  [ "$(date +\%d)" -le 7 ] && /usr/local/bin/job.sh
runwisp.toml
[tasks.job]
cron     = "0 9 * * 1"
timezone = "UTC"
run      = '[ "$(date +%d)" -le 7 ] && /usr/local/bin/job.sh'
cronRunWisp
The server was off at run time Skipped. anacron only catches up cron.daily, weekly and monthly. Runs the most recent missed run at start-up (catch_up = 1) and sends a missed-run alert.
The last run is still going Starts another copy on top. Queues it. on_overlap = "queue" is the default.
A run fails Output is mailed if MAILTO and a mail server work. No output, no mail, whatever the exit code. Marked failed and sent to your alert channels. retry_attempts adds retries.

Keep your crontab. sudo runwisp takeover runs it where it is and retires cron, crontab -e keeps working, and runwisp service uninstall puts cron back. It needs Linux with systemd; elsewhere, runwisp import cron converts the file. RunWisp doesn't do L, W or #, and it runs on one machine, not a cluster. Moving from cron →

FAQ

Questions

What does 0 0 9 ? * MON#1 mean?
At 09:00 on the first Monday of the month. In 2027 that's 12 runs on 12 days. Times are in the scheduler's time zone, usually the server's.
How do I run a job on the first Monday of the month in a crontab?
0 9 * * 1 [ "$(date +\%d)" -le 7 ] && command. Debian / Ubuntu cron takes 5 fields: minute hour day month weekday.
How do I run a job on the first Monday of the month with a systemd timer?
OnCalendar=Mon *-*-01..07 09:00:00 UTC. systemd timer uses OnCalendar=, not cron lines.
How do I run a job on the first Monday of the month in a Kubernetes CronJob?
Kubernetes CronJob takes 5 fields: minute hour day month weekday. Kubernetes CronJob can't run this: it needs a day of the month and a weekday at once.
How do I run a job on the first Monday of the month in GitHub Actions?
GitHub Actions takes 5 fields: minute hour day month weekday. GitHub Actions can't run this: it needs a day of the month and a weekday at once.
How do I run a job on the first Monday of the month on AWS EventBridge?
cron(0 9 ? * 2#1 *). AWS EventBridge Scheduler takes cron() with 6 fields, a year last, and ? in one day field.
More
Get started
GPL 3.0 · no accounts · free to self-host

Start in 30 seconds. Ship on your terms.

A single Go binary. If it doesn't earn the disk space, rm it. No account, no signup, no catch.

Install in 30 seconds
curl -fsSL https://get.runwisp.com | sh