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.
- 012026-08-19T14:17:45.000Z
- 022026-08-19T14:18:00.000Z
- 032026-08-19T14:18:45.000Z
- 042026-08-19T14:19:00.000Z
- 052026-08-19T14:19:45.000Z
- 062026-08-19T14:20:00.000Z
- 072026-08-19T14:20:45.000Z
- 082026-08-19T14:21:00.000Z
- 092026-08-19T14:21:45.000Z
- 102026-08-19T14:22:00.000Z
Variations
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). UsefixedDelay, notfixedRate: it measures the gap from the end of the previous run, so a slow run can’t cause stacking. See the Spring@Scheduledguide. - Rust —
tokio::time::interval(Duration::from_secs(45))in a loop. See Rust cron scheduling. - Python —
IntervalTrigger(seconds=45), which sidestepsCronTrigger’s field rules entirely. See the APScheduler guide. - systemd —
OnUnitActiveSec=45s, monotonic and DST-proof. SetAccuracySec=1sas 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?
Why doesn't `*/45 * * * * *` run every 45 seconds?
How do I run a job every 45 seconds on Linux?
How many times a day does a true 45-second schedule run?
Should I use cron or a timer for every 45 seconds?
Which other intervals have this same problem?
More seconds schedules
Other patterns on the same cadence — or browse every schedule.