The 0 0 1 * * Expression
The cron expression 0 0 1 * * fires at minute 0, hour 0, on day 1 of every month, regardless of which month or year. This means the job runs at midnight on January 1st, February 1st, March 1st, and so on, exactly 12 times per year (once per calendar month).
Field breakdown:
| Field | Value | Meaning |
|---|---|---|
| Minute | 0 | At the top of the hour |
| Hour | 0 | Midnight |
| Day of month | 1 | The 1st of the month |
| Month | * | Every month |
| Day of week | * | Any day |
The @monthly Alias
@monthly is shorthand for 0 0 1 * * and is supported by most cron daemons:
@monthly /opt/jobs/billing-cycle
# equivalent to:
0 0 1 * * /opt/jobs/billing-cycle
Unlike some shell aliases, @monthly is interpreted by the cron daemon itself, not by the shell. It has the same effect as the explicit five field form. If you are using Kubernetes CronJobs, AWS scheduled tasks, or GitHub Actions scheduled workflows, check the documentation. Most support @monthly, but a few require the five field syntax.
Edge Cases with Day of Month Values
Months with fewer than 31 days
If you schedule a job for day 29, 30, or 31, it will be silently skipped for months that do not have those days:
| Expression | Skipped months |
|---|---|
0 0 29 * * | February (most years) |
0 0 30 * * | February |
0 0 31 * * | Feb, Apr, Jun, Sep, Nov |
This is a significant reliability issue for billing, reporting, and compliance jobs. Use day 1 (0 0 1 * *) if you need the job to fire every single month without exception.
Leap year and February 29th
0 0 29 * * skips February entirely in nonleap years. In leap years, it fires on February 29th, but that only happens every four years (with century year exceptions). Do not use this expression for any job where consistent monthly execution is required.
The Last Day of the Month Problem
Cron has no built in syntax for “the last day of the month.” Some extended implementations (fcron, certain cloud schedulers) support the L operator, but it is nonstandard. The practical workaround is to run the job daily and check the date in the script:
#!/bin/bash
# Exit unless tomorrow is the 1st (i.e., today is the last day of the month)
if [ "$(date -d tomorrow +%d)" != "01" ]; then
exit 0
fi
# actual job logic here
Crontab entry:
0 23 * * * /opt/jobs/end-of-month-job
This fires the script every day at 11 PM, but the script itself exits immediately unless it is the last day of the month. It is not elegant, but it is portable and reliable.
The dom and dow OR Logic
When both the day of month field and the day of week field are specified (neither is *), cron uses OR logic, and the job runs when either condition is true:
0 0 1 * 1 # runs on the 1st of the month, AND on every Monday
This is almost never what you want when writing a monthly job. Leaving the day of week field as * (as in 0 0 1 * *) means “any day of week,” so the constraint is purely on the day of month. This is the correct form for a strict “first of the month” schedule.
Real-World Uses for Monthly Cron Jobs
Billing and invoicing
Generating invoices, processing subscription renewals, or recalculating usage based charges typically happens at the billing cycle boundary, the first of the month. Running at midnight on the 1st gives the job a full day before customers see the charge.
Monthly reporting
Finance, sales, and operational reports often roll up the previous month’s data. A job on the 1st ensures the prior month is complete and the data is final before the report runs. Add a small offset (like 0 1 1 * * for 1 AM) to ensure any end of month jobs that ran at midnight have finished.
Data archival
Moving data older than one month to cold storage, archiving old database records to a read only table, or exporting monthly snapshots to object storage are all natural fits for a monthly cron schedule.
Certificate and key rotation
Rotating API keys, refresh tokens, or secrets on a monthly cadence is a common security practice. A first of month cron provides a predictable, auditable rotation schedule.
Verifying the Schedule
from croniter import croniter
from datetime import datetime
cron = croniter("0 0 1 * *", datetime.now())
for _ in range(6):
print(cron.get_next(datetime))
Output will show the next 6 first of month midnight timestamps. Use this to confirm the schedule fires on the months and times you expect, especially if you are near a month boundary when setting it up.
Timezone Considerations
Like all cron jobs, 0 0 1 * * fires at midnight in the system timezone. If the server runs UTC and your billing cycle closes at midnight US Pacific time, the cron time needs to be adjusted:
- Pacific Standard Time (UTC-8): midnight PST =
0 8 1 * *in UTC - Pacific Daylight Time (UTC-7): midnight PDT =
0 7 1 * *in UTC
If the month boundary happens during a DST transition (which it can, since March 1st often falls near the spring forward date in the US), you need to handle the offset change. Running on UTC and doing timezone math in the application is the more reliable approach for any job where the exact fire time relative to a local timezone matters.