Cron expression for Twice a month (1st and 15th)
0 0 1,15 * *
Runs at midnight on the 1st and the 15th of every month.
Next 5 runs (your local time)
These are shown in your browser’s timezone. The job itself runs in the scheduler’s timezone — often UTC — so the real run time can differ.
What people actually schedule with 0 0 1,15 * *
- Semi-monthly payroll runs that pay on the 1st and 15th, the most common US pay cadence outside of bi-weekly
- Splitting a heavy monthly aggregation job into two lighter passes so a single run never balloons past your batch window
- Sending a 1st-of-month "new cycle" digest and a 15th "mid-month nudge" from the same job without standing up two separate crontab entries
Use 0 0 1,15 * * on your platform
It’s the same 5-field expression everywhere — what changes is where you put it and which timezone it runs in.
Linux / crontab
0 0 1,15 * * /path/to/your-command
Runs in the server’s local timezone — check it with timedatectl.
Full field reference: crontab(5) man page.
GitHub Actions
on:
schedule:
- cron: "0 0 1,15 * *"
GitHub Actions always runs scheduled jobs in UTC — there is no timezone setting, and runs can be delayed under load (official docs).
Kubernetes CronJob
spec:
schedule: "0 0 1,15 * *"
Defaults to UTC. Set spec.timeZone (Kubernetes 1.27+)
for a specific zone — see the
CronJob docs.
Quartz / Spring @Scheduled
Quartz uses 6 fields (seconds first): 0 0 0 1,15 * *. Watch out:
Quartz day-of-week is 1=SUN … 7=SAT (not 0–6), and day-of-month /
day-of-week use ? — double-check if your schedule touches those fields
(Quartz cron reference).
Gotchas with twice a month (1st and 15th) schedules
- This is two fixed dates, not "every ~15 days."
0 0 1,15 * *means the gap is 14 days (Jan 1→15) then 16–17 days (Jan 15→Feb 1). If your logic assumes an even fortnight — accruing interest, prorating, or rate-limiting — those uneven intervals will quietly skew the math. Compute elapsed days from a stored timestamp, don't assume 15. - The 1st collides with the busiest cron slot of the month. Midnight on the 1st is when logrotate, billing, monthly reports, and half your other jobs all fire at once, so this run fights them for I/O and DB locks. Stagger it a few minutes (
5 0 1,15 * *) or wrap the body inflock -n /tmp/semimonthly.lockso a slow start doesn't overlap the next scheduled task. - Both fire dates inherit the server timezone and the same DST footgun. Because both are at
0 0(midnight), a payroll cutoff defined in local time can land on the wrong calendar day under UTC cron, and around the November fall-back midnight is ambiguous (it occurs twice in some zones). SetCRON_TZexplicitly and make the job idempotent so a double-fire on the ambiguous hour can't double-pay.
Will you know if this job silently fails?
Cron jobs fail quietly — a server reboots, a path changes, or an error code is ignored — and nobody notices until the data is missing. A cron monitor (a dead-man’s-switch) alerts you when a scheduled job does not check in on time.
Monitor your cron jobs with UptimeRobot →
Disclosure: this is an affiliate link — we may earn a commission if you sign up, at no extra cost to you.
Is 0 0 1,15 * * the right schedule?
If you only need the mid-month half, drop to the 15th of the month and skip the 1st-of-month I/O contention entirely. If "twice a month" was really shorthand for "every two weeks," note that cron has no clean bi-weekly form — anchor a weekly Monday job and gate it on an even-week-number check in the script.
Or use the interactive cron generator & explainer, read the complete cron syntax guide, or pick another common schedule: