All recipes

Cron recipe

Cron first day of month: 0 0 1 * *, @monthly and first Monday

The expression

five-field cron
0 0 1 * *

Paste it into crontab -e, your platform cron config, or tool.crontap.com/cronjob-debugger to preview the next runs.

What it does

0 0 1 * * runs once a month at 00:00 on the 1st: minute 0, hour 0, day of month 1, every month, any day of the week. @monthly means exactly the same in Linux cron, Kubernetes and Crontap. Midnight is in the scheduler's timezone, which is UTC on most hosted platforms, so pick it on purpose.

When to use it

  • Monthly invoices and statements

    Generate PDFs and email customers once the previous month is closed.

  • Quota and credit resets

    Reset API quotas, credits or seat counts when the billing month starts.

  • Month-start reports

    Finance and ops summaries for the month that just ended. If they have to land on a working day, use the first-business-day variation.

Common variations

  • @monthly

    Same as 0 0 1 * *. GitHub Actions doesn't support it, and Vercel only documents the five explicit fields.

  • 0 9 1 * *

    09:00 on the 1st, for reports people read when they arrive.

  • 0 9 * * MON#1

    First Monday at 09:00 on Crontap. 1#1 works too, and SUN#1 gives the first Sunday. Don't paste it into Linux crontab; the pitfall below explains why.

  • 0 9 1-7 * * [ "$(date +\%u)" -eq 1 ] && /path/to/job

    First Monday on Linux: run on days 1 to 7, and only continue on Monday.

  • 0 9 1-7 * * [ "$(date +\%u)" -eq 7 ] && /path/to/job

    First Sunday on Linux. date +%u counts Sunday as 7, not cron's 0.

  • 0 9 1-3 * * [ "$(date +\%u)" -le 5 ] && { [ "$(date +\%d)" -eq 1 ] || [ "$(date +\%u)" -eq 1 ]; } && /path/to/job

    First business day on Linux: the 1st if it's a weekday, otherwise the Monday after. It ignores public holidays, and a cron previewer will list all of days 1 to 3 because the weekday check runs in the shell.

Same schedule in other schedulers

Same schedule in other schedulers
PlatformExpressionNote
Linux crontab (cronie, Debian, Ubuntu)0 0 1 * *@monthly is identical. Host timezone; cronie also reads CRON_TZ. No #, L or W.
Crontap0 0 1 * *@monthly works. First Monday is 0 9 * * MON#1. IANA timezone per schedule.
Kubernetes CronJob0 0 1 * *@monthly works. Set .spec.timeZone. No #, L or W.
systemd timerOnCalendar=*-*-01 00:00:00monthly is the same. Date and weekday must both match, so Mon *-*-01..07 09:00:00 is the first Monday.
GitHub Actions17 0 1 * *No @monthly. UTC unless timezone: is set. An off-minute avoids the busy top of the hour.
Vercel Cron0 0 1 * *Always UTC. It can't set both day fields, so there is no first-Monday form. Hobby fires within the hour (as of 2026-10).
AWS EventBridgecron(0 0 1 * ? *)First Monday is cron(0 9 ? * 2#1 *). Scheduler takes an IANA timezone; legacy rules are UTC.
Quartz (Java)0 0 0 1 * ?First Monday is 0 0 9 ? * 2#1. 1W is the first weekday of the month.
Google Cloud Scheduler0 0 1 * *Default UTC; set --time-zone. The gcloud CLI and API also take English schedules like "first sunday of month 12:00".

Where this schedule goes wrong

  • Days 1 to 7 plus Monday also means every Monday

    Cron fires when either day field matches, so 0 9 1-7 * 1 runs on all of days 1 to 7 and on every Monday after the 7th, about 10 or 11 times a month. Ubuntu's cron, the Kubernetes parser and Crontap's parser all behave this way (tested 2026-10).

  • Quartz's MON#1 fails quietly on Ubuntu

    MON#1 is Quartz, AWS and Crontap syntax. cronie rejects it. Ubuntu's cron accepts a # suffix, ignores it and runs on every matching weekday (tested 2026-10), so a first-Monday line fires every Monday. BusyBox drops the line as a comment.

  • Escape every %, and don't use == in the test

    Cron turns an unescaped % into a newline and cuts the command there, so date +%d quietly breaks the job. Write \%. Cron runs the line with /bin/sh, which is dash (a minimal shell) on Debian and Ubuntu, so compare with = or -eq inside [ ], never ==.

  • Midnight UTC on the 1st is still last month in the Americas

    GitHub Actions, Vercel, Cloud Scheduler and EventBridge rules default to UTC. On those, a 0 0 1 * * run lands at 20:00 on the last day of the previous month in New York during daylight saving time, and at 19:00 in winter. Set the timezone, or move the hour.

  • A server that's off at midnight skips the month

    Classic cron doesn't replay a missed minute, so a box that's down at 00:00 on the 1st skips that month. anacron, which runs /etc/cron.monthly on Debian and Ubuntu when it's installed, catches up on runs the machine missed while it was off, and so does a systemd timer with Persistent=true.

Run this with Crontap

If the month-start job is an HTTP endpoint, Crontap can call it on the 1st in your billing timezone, record each run's status code and duration, and alert you when it fails.

Related recipes

More patterns in the cron syntax cheat sheet and what is a cron job in Linux.

Related reading

FAQ

What is the cron expression for the first day of every month?
Use 0 0 1 * * (standard five-field cron). Verify the next run times in the free cron debugger before you deploy.
How do I run a cron job on the first Monday of the month?
On Crontap use 0 9 * * MON#1. Quartz uses 0 0 9 ? * 2#1, AWS EventBridge cron(0 9 ? * 2#1 *), and a systemd timer OnCalendar=Mon *-*-01..07 09:00:00. Linux crontab has no # syntax, so run on days 1 to 7 and check the weekday: 0 9 1-7 * * [ "$(date +\%u)" -eq 1 ] && /path/to/job. Don't use 0 9 1-7 * 1, which also runs every Monday.
How do I run a cron job on the first Sunday of the month?
Use 0 9 * * SUN#1 on Crontap. In Quartz and AWS, Sunday is 1, so write 0 0 9 ? * 1#1 or cron(0 9 ? * 1#1 *); on Crontap 1#1 is the first Monday. On Linux, use the first-Monday date check with 7 instead of 1, because date +%u counts Sunday as 7.
How do I run a cron job on the first business day of the month?
Quartz has 1W, which picks the first weekday of the month. Linux crontab and Crontap have no W, so run on days 1 to 3 and keep only the first weekday. On Linux, use the date check from the variations above. On Crontap, schedule 0 9 1-3 * * and have the endpoint return early on the other two days. Neither approach knows about public holidays.
Can Crontap run this schedule?
Yes, on the free Starter plan too, because it runs once a month. Paste 0 0 1 * * or @monthly, set the schedule's timezone (an IANA name like America/New_York) so midnight means your midnight, and use Perform test to call the endpoint once before you save. For the first Monday or first Sunday, Crontap accepts 0 9 * * MON#1 and 0 9 * * SUN#1. New schedules retry 5xx responses, timeouts and network failures with backoff, then send a failure alert by email, at most once a day on Starter. Pro, from $2.99/mo, adds Slack, Discord and Telegram alerts with intervals down to 1 minute.