Every 45 Minutes
*/45 * * * * `*/45 * * * *` does NOT run every 45 minutes — it fires at :00 and :45, leaving alternating 45- and 15-minute gaps. True 45-minute spacing needs a 3-line crontab aligned to a 3-hour cycle, or an interval timer.
Use in your stack
# TRUE "every 45 minutes" — aligned to midnight, repeats every 3 hours.
# Cron has no every-45-min step, so enumerate the 3-hour cycle in 3 lines:
0,45 */3 * * * /path/to/job # :00 and :45 at 00,03,06,...,21
30 1-23/3 * * * /path/to/job # :30 at 01,04,07,...,22
15 2-23/3 * * * /path/to/job # :15 at 02,05,08,...,23
# Produces 00:00, 00:45, 01:30, 02:15, 03:00, ... — 32 runs/day, all 45 min apart.
// Simplest correct approach: a fixed-rate timer, not cron.
// 45 minutes = 45 * 60 * 1000 = 2_700_000 ms
@Scheduled(fixedRate = 2_700_000)
public void every45Minutes() {
// ...
}
Next runs
Pick a timezone to see when this expression fires next.
- 012026-07-13T16:45:00.000Z
- 022026-07-13T17:00:00.000Z
- 032026-07-13T17:45:00.000Z
- 042026-07-13T18:00:00.000Z
- 052026-07-13T18:45:00.000Z
- 062026-07-13T19:00:00.000Z
- 072026-07-13T19:45:00.000Z
- 082026-07-13T20:00:00.000Z
- 092026-07-13T20:45:00.000Z
- 102026-07-13T21:00:00.000Z
Variations
Common use cases
- Spacing out API calls to respect a rate limit without clustering them on the hour.
- Staggering a job so it doesn't collide with hourly or half-hourly tasks.
- A deliberate off-beat cadence that drifts around the clock face.
Gotchas
- **`*/45 * * * *` is a trap.** The minute field is 0–59, so the `/45` step yields only 0 and 45 — the job fires at :00 and :45, then the counter resets at the top of the hour. Gaps alternate 45 min, 15 min, 45 min, 15 min. Watch the next-runs widget above to see it.
- 45 doesn't divide 60, so there is no single-line cron expression for a true every-45-minute interval. Any interval that isn't a divisor of 60 (7, 8, 40, 45…) has this problem.
- The correct crontab enumerates a 3-hour cycle (LCM of 45 and 60) across **three lines** — see below. It's aligned to midnight, so it only stays exact if the schedule starts on a clean midnight boundary.
- The 3-line crontab is wall-clock aligned, so DST transitions cause one irregular gap twice a year. A monotonic interval timer (Spring `fixedRate`, systemd `OnUnitActiveSec`) avoids that but drifts relative to the wall clock instead.
Type */45 * * * * into any cron parser — including the widget above — and you’ll see the problem: it fires at :00 and :45, then again at :00, :45. The gaps go 45 minutes, then 15 minutes, then 45, then 15. That’s not “every 45 minutes,” and it’s the single most common cron mistake for this interval.
Why the naive version breaks
Cron’s minute field runs 0–59. The step syntax */45 means “start at 0, keep adding 45 while you’re still in range”: minute 0, minute 45, then 90 — which is out of range, so it stops. At the top of the next hour the field resets to 0 and the same two values repeat. Cron matches fields against the wall clock; it has no concept of “time since the last run,” so it can’t carry a 45-minute offset across the hour boundary.
The root cause is arithmetic: 45 is not a divisor of 60. Intervals like 15, 20, and 30 minutes divide the hour evenly, so their */n forms repeat cleanly. 45 leaves a remainder, so the schedule only “closes” over a 3-hour window — the least common multiple of 45 and 60.
The correct crontab: enumerate the 3-hour cycle
A true 45-minute interval starting at midnight fires at 00:00, 00:45, 01:30, 02:15, then 03:00 — where the cycle repeats. Within each 3-hour block the minute lands on a different value per hour, so you need three crontab lines:
0,45 */3 * * * /path/to/job # :00 and :45 at 00,03,06,...,21
30 1-23/3 * * * /path/to/job # :30 at 01,04,07,...,22
15 2-23/3 * * * /path/to/job # :15 at 02,05,08,...,23
Because a day (1440 minutes) is exactly 32 × 45, and 24 hours tiles evenly into eight 3-hour cycles, this produces a perfect 45-minute gap all day — including the wrap from the last run at 23:15 to 00:00 the next day. Run it through the cron parser line by line to confirm.
The one caveat: it’s aligned to midnight. It assumes you want the runs on that specific grid, and DST transitions will make one gap an hour off twice a year.
The simpler answer: use a timer
If you don’t need runs pinned to specific clock times, skip cron entirely. An interval timer expresses “every 45 minutes” in one line and never has this problem:
- Spring —
@Scheduled(fixedRate = 2_700_000)(45 min in milliseconds). See the Spring @Scheduled guide. - Rust —
tokio::time::interval(Duration::from_secs(2700))in a loop. See Rust cron scheduling. - systemd — a timer unit with
OnUnitActiveSec=45min, which is monotonic and rides through DST cleanly. See systemd timers.
The trade-off is the mirror image of the crontab approach: timers measure from the previous run (or process start), so they stay a strict 45 minutes apart but drift relative to the wall clock. Pick the crontab when the clock times matter; pick the timer when the interval matters. For the general version of this problem, see how cron expressions work.
Frequently asked questions
What is the cron expression for every 45 minutes?
Why doesn't `*/45 * * * *` run every 45 minutes?
How do I actually run a job every 45 minutes?
Why is 45 minutes harder than 30 or 15 minutes?
Does the 3-line every-45-minutes crontab handle daylight saving time?
Browse all cron patterns
Every schedule Cronuru documents, with its expression and code snippets for six dialects.