cronuru
Pattern

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.

Next 10 runs
  1. 012026-07-13T16:45:00.000Z
  2. 022026-07-13T17:00:00.000Z
  3. 032026-07-13T17:45:00.000Z
  4. 042026-07-13T18:00:00.000Z
  5. 052026-07-13T18:45:00.000Z
  6. 062026-07-13T19:00:00.000Z
  7. 072026-07-13T19:45:00.000Z
  8. 082026-07-13T20:00:00.000Z
  9. 092026-07-13T20:45:00.000Z
  10. 102026-07-13T21:00:00.000Z

Variations

*/30 * * * *

Every 30 minutes

*/20 * * * *

Every 20 minutes

*/15 * * * *

Every 15 minutes

0 * * * *

Every hour

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.
  • Rusttokio::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?
There isn't a single clean one. The common attempt, `*/45 * * * *`, does not work — it fires at :00 and :45 of each hour, giving alternating 45- and 15-minute gaps. A true 45-minute interval, aligned to midnight, needs three crontab lines covering a 3-hour cycle: `0,45 */3 * * *`, `30 1-23/3 * * *`, and `15 2-23/3 * * *`. Together they fire at 00:00, 00:45, 01:30, 02:15, 03:00, and so on.
Why doesn't `*/45 * * * *` run every 45 minutes?
The minute field only counts 0–59. The step `*/45` means "start at 0, add 45": that gives minute 0 and minute 45, and the next step (90) is out of range, so the counter resets at the top of the next hour. The result is fires at :00 and :45 — a 45-minute gap followed by a 15-minute gap, repeating. Cron matches wall-clock fields; it has no memory of "45 minutes since the last run."
How do I actually run a job every 45 minutes?
Two options. (1) Wall-clock aligned: use the 3-line crontab above, which enumerates the 3-hour cycle. (2) Simpler, if exact clock alignment doesn't matter: use an interval timer — Spring `@Scheduled(fixedRate = 2700000)`, Rust `tokio::time::interval(Duration::from_secs(2700))`, or a systemd timer with `OnUnitActiveSec=45min`. Timers express "every 45 minutes" directly but measure from the previous run rather than the clock.
Why is 45 minutes harder than 30 or 15 minutes?
Because 45 is not a divisor of 60. Intervals like 15, 20, and 30 divide evenly into an hour, so `*/15`, `*/20`, and `*/30` repeat cleanly every hour. 45 leaves a remainder, so the pattern only closes over a 3-hour window (the least common multiple of 45 and 60). Any non-divisor interval — 7, 8, 40, 45, 50 minutes — has the same issue and needs either enumeration or a timer.
Does the 3-line every-45-minutes crontab handle daylight saving time?
Not perfectly. It's aligned to wall-clock times, so at a DST transition one interval will be an hour short or long (the skipped or repeated hour). For most jobs that's harmless. If you need a strictly monotonic 45-minute gap through DST, use an interval timer like systemd's `OnUnitActiveSec=45min`, which measures elapsed time rather than matching the clock.

Browse all cron patterns

Every schedule Cronuru documents, with its expression and code snippets for six dialects.