cronuru
Pattern

Every 45 Seconds

*/45 * * * * *
*/45 * * * * ?

`*/45 * * * * *` does NOT run every 45 seconds — it fires at :00 and :45, leaving alternating 45- and 15-second gaps. True spacing needs a 4-entry 3-minute cycle, or an interval timer.

Use in your stack

# crontab has no seconds field, AND 45 doesn't divide 60 — so the
# cycle is 3 minutes (LCM of 45s and 60s) across four entries.
# Firings land at t=0s, 45s, 90s, 135s of each 3-minute cycle.
*/3     * * * * flock -n /tmp/probe.lock /usr/local/bin/probe.sh
*/3     * * * * sleep 45; flock -n /tmp/probe.lock /usr/local/bin/probe.sh
1-59/3  * * * * sleep 30; flock -n /tmp/probe.lock /usr/local/bin/probe.sh
2-59/3  * * * * sleep 15; flock -n /tmp/probe.lock /usr/local/bin/probe.sh

# systemd's OnCalendar CANNOT express this — it matches wall-clock
# fields, so it hits the same non-divisor wall. Use the monotonic
# timer instead, which measures from the last run:
#   [Timer]
#   OnUnitActiveSec=45s
#   AccuracySec=1s        # defaults to 1min
{
  "schedules": [
    { "cron": "0 0/3 * * * ?",  "timezone": "UTC" },
    { "cron": "45 0/3 * * * ?", "timezone": "UTC" },
    { "cron": "30 1/3 * * * ?", "timezone": "UTC" },
    { "cron": "15 2/3 * * * ?", "timezone": "UTC" }
  ]
}
// WRONG — fires at :00 and :45, gaps alternate 45s / 15s.
@Scheduled(cron = "*/45 * * * * *")
public void notEvery45Seconds() { }

// CORRECT via cron — enumerate the 3-minute cycle across 4 schedules.
// 6-field: second minute hour day-of-month month day-of-week
@Scheduled(cron = "0 */3 * * * *")      // :00 at minutes 0,3,6,...
@Scheduled(cron = "45 */3 * * * *")     // :45 at minutes 0,3,6,...
@Scheduled(cron = "30 1-59/3 * * * *")  // :30 at minutes 1,4,7,...
@Scheduled(cron = "15 2-59/3 * * * *")  // :15 at minutes 2,5,8,...

// SIMPLEST — no cron parser, no cycle to enumerate, no overlap.
@Scheduled(fixedDelay = 45_000)
public void every45Seconds() { }

Next runs

Pick a timezone to see when this expression fires next.

Next 10 runs
  1. 012026-08-19T14:17:45.000Z
  2. 022026-08-19T14:18:00.000Z
  3. 032026-08-19T14:18:45.000Z
  4. 042026-08-19T14:19:00.000Z
  5. 052026-08-19T14:19:45.000Z
  6. 062026-08-19T14:20:00.000Z
  7. 072026-08-19T14:20:45.000Z
  8. 082026-08-19T14:21:00.000Z
  9. 092026-08-19T14:21:45.000Z
  10. 102026-08-19T14:22:00.000Z

Variations

*/30 * * * * *

Every 30 seconds (divides 60 — one clean entry)

*/15 * * * * *

Every 15 seconds (divides 60 — one clean entry)

*/45 * * * *

Every 45 minutes — the same trap, one unit up

Common use cases

  • Polling a rate-limited API on an off-beat cadence so calls don't cluster on the minute.
  • Staggering a health check so it never collides with per-minute or per-30-second jobs.
  • A deliberately non-aligned probe interval that walks around the minute.

Gotchas

  • **Standard Unix cron cannot do this at all.** The 5-field format has no seconds field — its floor is one minute. Kubernetes CronJob, AWS EventBridge and Cloudflare Workers inherit that floor, and GitHub Actions floors at five minutes. You need Spring, Quartz, node-cron, APScheduler or a similar seconds-aware dialect even to begin.
  • **`*/45 * * * * *` is a trap.** The seconds field is 0–59, so the `/45` step yields only 0 and 45. The job fires at second :00 and second :45, then the counter resets at the top of the minute. Gaps alternate 45s, 15s, 45s, 15s.
  • 45 doesn't divide 60, so no single expression gives a true 45-second interval. Any interval that isn't a divisor of 60 — 7, 8, 25, 40, 45 — has exactly this problem, at any unit.
  • The correct form enumerates a **3-minute cycle** (the LCM of 45 and 60 seconds) across **four** entries. It tiles the hour and the day evenly, so the gap stays exactly 45s across minute, hour and midnight boundaries.
  • **1,920 runs per day is a lot of scheduler overhead.** Re-evaluating four cron expressions every second to produce one firing every 45 is far more machinery than a `sleep 45` loop. If the runs don't need to land on specific clock seconds, don't use cron here.
  • Overlap is a real risk at this cadence — if a run ever exceeds 45 seconds, firings stack up. Prefer `fixedDelay` semantics (gap measured from the *end* of the previous run) over the `fixedRate` behaviour a cron expression gives you.

Type */45 * * * * * into any seconds-aware cron parser — including the widget above — and the problem shows up immediately: it fires at second :00 and second :45, then again at :00, :45. The gaps run 45 seconds, then 15, then 45, then 15. That isn’t “every 45 seconds.”

This interval is unusual in that it trips over two separate limitations at once.

Problem one: standard cron has no seconds

Before the step arithmetic even matters, plain Unix crontab is out. Its five fields start at minutes, so its floor is one minute — see sub-minute scheduling for which runtimes can go below that and what each one’s real minimum is. Kubernetes CronJob, AWS EventBridge and Cloudflare Workers all inherit the one-minute floor; GitHub Actions floors at five minutes.

So you need a 6-field, seconds-first dialect — Spring, Quartz, node-cron, APScheduler — just to get in the door.

Problem two: 45 doesn’t divide 60

Once you’re in a seconds-aware dialect, the same arithmetic that breaks every 45 minutes breaks this one unit down.

The seconds field runs 0–59. */45 means “start at 0, keep adding 45 while still in range”: second 0, second 45, then 90 — out of range, so it stops. At the top of the next minute the field resets and those same two values repeat. Cron matches fields against the wall clock; it has no memory of when it last ran, so it can’t carry a 45-second offset across the minute boundary.

Intervals like 15, 20 and 30 seconds divide the minute evenly, so their */n forms repeat cleanly. 45 leaves a remainder, so the schedule only closes over a 3-minute window — the least common multiple of 45 and 60 seconds.

The correct form: enumerate the 3-minute cycle

A true 45-second interval starting on the minute fires at 00:00, 00:45, 01:30, 02:15, then 03:00 where the cycle repeats. Each firing lands on a different second-of-minute, so you need four entries:

0  */3    * * * *    # second :00 at minutes 0,3,6,...,57
45 */3    * * * *    # second :45 at minutes 0,3,6,...,57
30 1-59/3 * * * *    # second :30 at minutes 1,4,7,...,58
15 2-59/3 * * * *    # second :15 at minutes 2,5,8,...,59

In Quartz idiom, using n/3 start-step syntax and ? for the unused day-of-week field:

0  0/3 * * * ?
45 0/3 * * * ?
30 1/3 * * * ?
15 2/3 * * * ?

This tiles cleanly. An hour is 3,600 seconds and the cycle is 180, so 3,600 ÷ 180 = 20 whole cycles per hour — 80 firings per hour, every gap exactly 45 seconds, including the wrap from the last firing at 59:15 to 00:00 of the next hour. Because the hour divides evenly, midnight needs no special handling either. Over a day that’s 1,920 runs, which is exactly 86,400 ÷ 45.

On Linux: the crontab form, and why systemd’s OnCalendar won’t help

Plain crontab has no seconds field, so the four entries above have to be expressed as minute schedules with sleep offsets carrying the seconds — four lines covering the same 3-minute cycle (full block in the Linux tab above).

The part worth knowing: systemd’s OnCalendar= cannot express this either. OnCalendar matches wall-clock fields exactly the way cron does, so it hits the identical non-divisor wall — there is no *:*:00/45 that yields a steady 45-second gap. Reaching for a systemd timer doesn’t rescue you here the way it does at 10 or 30 seconds.

What does work is the monotonic timer, which measures from the last run rather than against the clock:

[Timer]
OnUnitActiveSec=45s
AccuracySec=1s

AccuracySec still matters — it defaults to 1 minute, which would swamp a 45-second interval outright. See systemd timers.

The simpler answer: use a timer

Look at what the correct version costs: four schedule entries, each re-evaluated every second, to produce one firing every 45. And unlike every 45 minutes — where wall-clock alignment might genuinely matter — there is nothing meaningful to align to at a 45-second cadence. The runs walk around the minute face no matter what.

So express it as what it actually is, a polling interval:

  • Spring@Scheduled(fixedDelay = 45_000). Use fixedDelay, not fixedRate: it measures the gap from the end of the previous run, so a slow run can’t cause stacking. See the Spring @Scheduled guide.
  • Rusttokio::time::interval(Duration::from_secs(45)) in a loop. See Rust cron scheduling.
  • PythonIntervalTrigger(seconds=45), which sidesteps CronTrigger’s field rules entirely. See the APScheduler guide.
  • systemdOnUnitActiveSec=45s, monotonic and DST-proof. Set AccuracySec=1s as well, or the default 1-minute accuracy window will swamp a 45-second interval completely. See systemd timers.

Reach for the four-entry cron cycle only when something downstream genuinely requires the firings to land on those specific clock seconds.

Frequently asked questions

What is the cron expression for every 45 seconds?
There isn't a single one. 45 doesn't divide evenly into 60, so no single cron expression produces a steady 45-second interval — `*/45 * * * * *` fires only at second :00 and second :45, alternating 45- and 15-second gaps. A true interval needs four entries covering a 3-minute cycle: seconds :00 and :45 on minutes 0,3,6…, second :30 on minutes 1,4,7…, and second :15 on minutes 2,5,8…. Standard Unix cron can't do it at all, since it has no seconds field.
Why doesn't `*/45 * * * * *` run every 45 seconds?
Because the seconds field only runs 0–59. The step syntax `*/45` means "start at 0 and keep adding 45 while still in range": it produces 0, then 45, then 90 — which is out of range, so it stops. At the top of the next minute the field resets to 0 and the same two values repeat. Cron matches fields against the wall clock and has no notion of "time since the last run," so it cannot carry a 45-second offset across the minute boundary.
How do I run a job every 45 seconds on Linux?
Four crontab entries covering a 3-minute cycle — the LCM of 45 and 60 seconds. Plain on minutes 0,3,6…, then `sleep 45` on the same minutes, `sleep 30` on minutes 1,4,7…, and `sleep 15` on minutes 2,5,8…. Note that systemd's `OnCalendar=` **cannot** express this: it matches wall-clock fields and hits the same non-divisor wall cron does. The monotonic timer can — `OnUnitActiveSec=45s` with `AccuracySec=1s` — because it measures from the end of the last run rather than against the clock.
How many times a day does a true 45-second schedule run?
1,920 times — 86,400 seconds in a day divided by 45. The four-entry cycle produces exactly 80 firings per hour with every gap exactly 45 seconds, including the wrap from 59:15 to the next hour's 00:00. That volume is worth noticing: it's a strong argument for a plain interval loop rather than four cron expressions being evaluated every second.
Should I use cron or a timer for every 45 seconds?
A timer, in nearly every case. A cron expression describes alignment to the wall clock, and at a 45-second cadence there's nothing meaningful to align to — the runs walk around the minute regardless. `@Scheduled(fixedDelay = 45_000)`, `tokio::time::interval(Duration::from_secs(45))`, or `setInterval(fn, 45_000)` each express it in one line, need no cycle enumeration, and let you pick whether the gap starts before or after the work runs.
Which other intervals have this same problem?
Every interval that isn't a divisor of 60. For seconds that means 7, 8, 9, 11, 13, 14, 16, 25, 35, 40, 45, 50 and so on — anything where `60 % n != 0`. The divisors of 60 (1, 2, 3, 4, 5, 6, 10, 12, 15, 20, 30) are the only values where a single `*/n` expression gives a steady interval. The same arithmetic applies to the minute field, which is why [every 45 minutes](/every-45-minutes) fails in exactly the same way.

More seconds schedules

Other patterns on the same cadence — or browse every schedule.