Cron recipe
Cron first day of month: 0 0 1 * *, @monthly and first Monday
The expression
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
@monthlySame 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#1First 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/jobFirst Monday on Linux: run on days 1 to 7, and only continue on Monday.
0 9 1-7 * * [ "$(date +\%u)" -eq 7 ] && /path/to/jobFirst 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/jobFirst 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
| Platform | Expression | Note |
|---|---|---|
| Linux crontab (cronie, Debian, Ubuntu) | 0 0 1 * * | @monthly is identical. Host timezone; cronie also reads CRON_TZ. No #, L or W. |
| Crontap | 0 0 1 * * | @monthly works. First Monday is 0 9 * * MON#1. IANA timezone per schedule. |
| Kubernetes CronJob | 0 0 1 * * | @monthly works. Set .spec.timeZone. No #, L or W. |
| systemd timer | OnCalendar=*-*-01 00:00:00 | monthly is the same. Date and weekday must both match, so Mon *-*-01..07 09:00:00 is the first Monday. |
| GitHub Actions | 17 0 1 * * | No @monthly. UTC unless timezone: is set. An off-minute avoids the busy top of the hour. |
| Vercel Cron | 0 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 EventBridge | cron(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 Scheduler | 0 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 * 1runs 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#1is 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, sodate +%dquietly 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-eqinside[ ], 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.monthlyon Debian and Ubuntu when it's installed, catches up on runs the machine missed while it was off, and so does a systemd timer withPersistent=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
- the last day of every month
0 0 L * * - every Monday at 9am
0 9 * * 1 - every weekday
0 9 * * 1-5 - once a day
0 0 * * *
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.