The expression
0 0 */2 * *
Runs at midnight on the 1st, 3rd, 5th … 29th and 31st of every month: on odd-numbered days, not strictly every 48 hours.
| Field | Value | Meaning |
|---|---|---|
| Minute | 0 |
at minute 0 |
| Hour | 0 |
at midnight |
| Day of month | */2 |
every 2nd day, starting at 1: 1, 3, 5 … 31 |
| Month | * |
every month |
| Day of week | * |
any day of the week |
Why it isn't exact
A step like */2 counts within the field, and the day-of-month field starts again at 1 every month. So the pattern breaks at month ends:
- After a 31-day month: runs on the 31st and the 1st, two days in a row.
- After a 30-day month: the 29th, then the 1st, a normal two-day gap.
- February: the 27th then 1 March in common years; in leap years the 29th and then the 1st, back to back again.
That's seven back-to-back pairs a year. For "roughly every other day" (cleaning temp files, refreshing a cache) it doesn't matter. For anything billed, rotated or rate-limited per 48 hours, it does.
Exactly every 48 hours
Standard cron can't count days across months, so the usual fix is to run daily and let the command skip alternate days, using the day number since the Unix epoch:
0 0 * * * [ $(( $(date +\%s) / 86400 \% 2 )) -eq 0 ] && /usr/local/bin/job.sh
% must be written \% inside a crontab, because cron turns a bare % into a newline. The epoch day count is in UTC; if the server's midnight isn't close to UTC midnight, pick a run time such as noon so daylight saving can never push two runs into the same UTC day.
Other ways to get a true interval:
- systemd timer:
OnUnitActiveSec=2d(withOnBootSec=for the first run) counts 48 hours from the last run.OnCalendar=*-*-1/2has the same odd-day problem as cron. - AWS EventBridge:
rate(2 days)is a real two-day interval from when the rule was created.
Variants
| Schedule | Expression |
|---|---|
| Even days (2, 4 … 30) | 0 0 2-30/2 * * |
| Every other day at 9am | 0 9 */2 * * |
| Monday, Wednesday, Friday | 0 0 * * 1,3,5 |
| Every 3 days (1, 4, 7 … 31) | 0 0 */3 * * |
| Every day | 0 0 * * * |
| Every other week | workaround |
Monday-Wednesday-Friday is often what "every other day" really means at work, and it's exact every week.
Where to write it
- crontab:
0 0 */2 * * command, or the daily line with the epoch test above. - GitHub Actions:
cron: '0 0 */2 * *'in UTC, with the same month-end behaviour. For an exact interval, schedule daily and add a first step that exits when$(( $(date +%s) / 86400 % 2 ))is 1 (no escaping outside crontab). - Kubernetes CronJob:
schedule: "0 0 */2 * *"withtimeZone:; same caveat. - AWS EventBridge:
cron(0 0 1/2 * ? *)for the odd-day pattern, orrate(2 days)for the exact one.
Paste it into the Cron Expression Parser and look at the runs around the end of a 31-day month.