Twice a Month Cron Expression
Runs on the 1st and 15th of every month at midnight.
0 0 1,15 * * in POSIX cron
Expression
0 0 1,15 * *Try it in the tester →Field Breakdown
How It Works
Day-of-month list `1,15` — fires twice per month at 00:00, on day 1 and day 15.
Example Run Times
- Jan 1 00:00:00
- Jan 15 00:00:00
- Feb 1 00:00:00
- Feb 15 00:00:00
- Mar 1 00:00:00
Frequently Asked Questions
Will this fire 24 times per year?
Yes — exactly 24 runs (12 months × 2 days each).
0 0 1,15 * * in Quartz / Spring
A two-value list in day-of-month gives 24 firings a year: midnight on the 1st and midnight on the 15th. It is the cron spelling of a semimonthly cycle, the rhythm payroll and statement systems run on.
Quartz / Spring expression
0 0 0 1,15 * ?Try it in the tester →At 00:00:00 on day 1, 15 of the month
Unix / POSIX equivalent: 0 0 1,15 * *
Field Breakdown
Using It with Quartz / Spring
Semimonthly schedules dominate billing-adjacent work — second payroll legs, mid-cycle and start-of-cycle statement generation, twice-monthly reconciliation against a payment processor. Spring accepts the list form unchanged in @Scheduled(cron = "0 0 0 1,15 * ?"); Quartz parses it identically in a CronTrigger. If the 1st and the 15th do different things (open a period versus checkpoint it), resist the urge to branch on the date inside one job — two triggers with one literal day each are easier to reason about, pause independently, and show up separately in scheduler dashboards.
Quartz-Specific Notes
- →The two firings split the month unevenly: 14 days from the 1st to the 15th, then 14 to 17 days back to the next 1st depending on month length. Code that assumes a fixed half-month interval will drift.
- →Lists in the day-of-month field can grow — 1,8,15,22 produces a near-weekly date-anchored cadence — but each value must be a plain day number.
- →Quartz will not accept L inside a day list, so "the 15th and the last day" cannot be one expression.
Frequently Asked Questions
How do I schedule the 15th and the last day of the month, like classic semimonthly payroll?
Two triggers pointed at the same JobDetail: 0 0 0 15 * ? and 0 0 0 L * ?. Quartz's parser doesn't allow L to appear alongside other values in a day-of-month list, but multiple triggers per job are first-class.
Are the runs always exactly two weeks apart?
No — only the 1st-to-15th leg is a constant 14 days. The return leg from the 15th to the next 1st stretches from 14 days (non-leap February) to 17 days (31-day months). It's calendar-semimonthly, not biweekly; if you need strict 14-day spacing, anchor to a weekday instead and skip alternate weeks in job logic, since cron has no every-other-week concept.
0 0 1,15 * * in AWS EventBridge
A value list in day-of-month — 1,15 — produces 24 fires a year at 00:00 UTC, opening the month and marking its midpoint. This is the semi-monthly rhythm of payroll files, billing waves, and any process the business thinks of as "the 1st and the 15th".
AWS EventBridge expression
cron(0 0 1,15 * ? *)Try it in the tester →At 00:00 UTC on day 1, 15 of the month
Unix / POSIX equivalent: 0 0 1,15 * *
Field Breakdown
Using It with AWS EventBridge
Attach it to a Step Functions workflow that runs the semi-monthly batch: generating payment files for a payroll provider, cutting the two invoice waves a finance team staggers to smooth cash flow, or refreshing a partner data share that is contractually delivered twice a month. Both fires hit the same target with the same configuration, so workflows that must behave differently on the 1st versus the 15th branch on the day extracted from the event's scheduled timestamp rather than existing as two separate rules.
AWS-Specific Notes
- →The spacing is uneven by construction: the 1st-to-15th leg is always 14 days, but the 15th-to-1st leg runs 14 days in February and up to 17 after a 31-day month.
- →Semi-monthly (24 runs a year) and biweekly (26) diverge by two runs annually — finance stakeholders usually care which one they asked for, so confirm before encoding this.
- →Listing days in day-of-month forces day-of-week to ?; there is no way to additionally require those dates to be weekdays in the same expression — EventBridge demands exactly one of the day fields be ?, so a date list and a weekday constraint cannot coexist.
Frequently Asked Questions
I actually need every 14 days, not the 1st and 15th — what should I use?
Cron cannot count fortnights across month boundaries. Create an EventBridge Scheduler schedule with rate(14 days) instead; it holds a true two-week interval, anchored to the schedule's start time rather than to calendar dates.
How does my Lambda know whether this is the 1st-of-month or 15th-of-month run?
Read the time field of the scheduled event and check the day component. Relying on the function's own clock works too but breaks on retries that cross midnight; the event timestamp does not.
0 0 1,15 * * in GitHub Actions
A comma list in the day-of-month field: runs at 00:00 UTC on the 1st and the 15th, 24 times a year. This semimonthly pattern matches how a lot of business processes actually split the month — payroll halves, billing cycles, twice-monthly statements.
GitHub Actions expression
0 0 1,15 * *Try it in the tester →At 00:00 UTC on day 1, 15 of the month
Unix / POSIX equivalent: 0 0 1,15 * *
Field Breakdown
Using It with GitHub Actions
As on: schedule: - cron: "0 0 1,15 * *" in a default-branch workflow, the two-ticks-per-month rhythm suits invoice or statement generation on business-calendar boundaries, a dependency and license report frequent enough to stay current but not noisy, or certificate-expiry sweeps where weekly would be overkill. The list syntax is doing the work here — each comma-separated value is an independent match in the same field — and it extends naturally if the process ever grows a third date, say 1,11,21.
GitHub-Specific Notes
- →The spacing is asymmetric: always exactly 14 days from the 1st to the 15th, but 14 to 17 days from the 15th back to the next 1st depending on the month's length — logic that assumes a constant interval will drift.
- →This is a list, not a range: writing 1-15 by mistake would fire on fifteen consecutive days instead of two.
- →Both ticks sit on UTC calendar boundaries that billing systems also care about; if the job reads financial data, confirm whether that system's day rolls over in UTC or somewhere else.
Frequently Asked Questions
Is twice a month the same as every two weeks?
No — 1,15 drifts against a true fortnight because months are not 28 days. For a strict 14-day cycle, schedule weekly (for example 0 0 * * 1) and have a guard step gate on epoch days: [ $(( $(date +%s) / 86400 % 14 )) -lt 7 ] || exit 0. Avoid ISO week parity (date +%V) — it slips at 53-week year boundaries.
Could I write 0 0 */14 * * to get the same result?
No. A step in the day-of-month field counts from 1, so */14 matches days 1, 15, and 29 — a third, unwanted run on the 29th in every month that has one. The explicit list 1,15 is the only correct spelling of semimonthly.