cronuru
Pattern

Every 20 Seconds

*/20 * * * * *
0/20 * * * * ?

Runs every 20 seconds — at :00, :20, :40 of each minute. Requires a seconds-aware dialect (Spring or Quartz); standard Unix cron is minute-precision only.

Use in your stack

# crontab has no seconds field — three entries tile the minute at
# :00, :20 and :40. flock -n skips rather than stacks a slow run.
* * * * * flock -n /tmp/probe.lock /usr/local/bin/probe.sh
* * * * * sleep 20; flock -n /tmp/probe.lock /usr/local/bin/probe.sh
* * * * * sleep 40; flock -n /tmp/probe.lock /usr/local/bin/probe.sh

# Or a systemd timer — /etc/systemd/system/probe.timer
# Without AccuracySec the 1min default coalesces all three firings.
#   [Timer]
#   OnCalendar=*-*-* *:*:00/20
#   AccuracySec=1s
{
  "schedule": {
    "cron": "0/20 * * * * ?",
    "timezone": "UTC"
  }
}
// 6-field: second minute hour day-of-month month day-of-week
@Scheduled(cron = "*/20 * * * * *")
public void everyTwentySeconds() {
    // ...
}

// Simpler for pure intervals — no cron parser needed:
@Scheduled(fixedRate = 20000) // milliseconds
public void everyTwentySecondsFixedRate() {
    // ...
}

Next runs

Pick a timezone to see when this expression fires next.

Next 10 runs
  1. 012026-09-09T15:00:00.000Z
  2. 022026-09-09T15:00:20.000Z
  3. 032026-09-09T15:00:40.000Z
  4. 042026-09-09T15:01:00.000Z
  5. 052026-09-09T15:01:20.000Z
  6. 062026-09-09T15:01:40.000Z
  7. 072026-09-09T15:02:00.000Z
  8. 082026-09-09T15:02:20.000Z
  9. 092026-09-09T15:02:40.000Z
  10. 102026-09-09T15:03:00.000Z

Variations

*/15 * * * * *

Every 15 seconds

*/10 * * * * *

Every 10 seconds

*/30 * * * * *

Every 30 seconds

Common use cases

  • Polling a moderately active queue or message broker.
  • Refreshing a live metric or status panel during development.
  • Sub-minute health checks with a 20-second detection window.
  • Periodically flushing a small buffer to a downstream system.

Gotchas

  • **Unix crontab cannot do this in one line.** The 5-field format has no seconds field; its minimum is `* * * * *` (every minute). Kubernetes CronJob and GitHub Actions inherit the same limit. Three offset entries get you there — see the Linux tab above.
  • **A systemd timer here needs `AccuracySec=1s`.** It defaults to **1 minute**, so `OnCalendar=*-*-* *:*:00/20` on its own lets systemd coalesce the three firings into one. The unit looks correct and runs a third as often as intended.
  • In Spring, `*/20 * * * * *` is the 6-field form (seconds first). Don't confuse it with Unix's 5-field `*/20 * * * *`, which means every 20 *minutes*.
  • In Quartz, use `0/20 * * * * ?` — the `0/20` seconds field means "starting at second 0, every 20 seconds," and `?` fills the unused day-of-week field.
  • For a pure interval, a `fixedRate`/`fixedDelay` loop is simpler than a cron parser. Use `@Scheduled(fixedRate = 20000)` in Spring or `tokio::time::interval` in Rust.
  • Because 60 divides evenly by 20, the runs land cleanly at :00, :20, :40 — no drift within the minute.

Three times a minute is an awkward cadence for cron, because cron’s finest unit is the minute itself. The 5-field format in /etc/crontab — and in Kubernetes CronJobs and GitHub Actions, which borrow it — starts counting at minutes and has nowhere to put a 20.

On Linux, without changing schedulers

Sixty divided by twenty is three, so three crontab lines cover the minute exactly: one on the minute, then sleep 20 and sleep 40 (full block in the Linux tab above). Three is a comfortable number here — the crontab stays readable and you get three log streams rather than the twelve a 5-second job would produce.

What you’re trading away is precision. sleep 20 begins counting when cron forks the job, not at the top of the minute, so on a loaded box the two offset copies gradually slide. Wrapping each in flock -n at least guarantees a run that overshoots its 20-second budget gets dropped rather than piling onto the next.

If the machine runs systemd, skip all of that:

[Timer]
OnCalendar=*-*-* *:*:00/20
AccuracySec=1s

The AccuracySec line is doing real work. Leave it out and it falls back to 1 minute, which permits systemd to fold all three firings into a single run — a unit file that reads exactly right while firing a third as often as you intended. systemd timers covers the timer+service pair this needs.

A stagger note

Twenty seconds is a useful interval precisely because it doesn’t line up with the common ones. A 20-second job and a 30-second job share only the top of the minute; a 20 and a 15 share only :00 as well. If you’re spacing out health checks so they don’t all fire together and spike load, that non-alignment is the reason to choose 20 over 15 or 30.

Or use a dialect that has seconds

To go sub-minute you need a 6-field, seconds-first dialect:

For an interval this tight, a plain timer is usually the better tool. Spring’s @Scheduled(fixedRate = 20000) and Rust’s tokio::time::interval(Duration::from_secs(20)) both express “every 20 seconds” without a cron parser — and they’re easier to reason about when a run might overrun its window. See also every 15 seconds and every 30 seconds.

See sub-minute scheduling for which runtimes support seconds at all, and what to do on the ones that don’t.

Frequently asked questions

What is the cron expression for every 20 seconds?
`*/20 * * * * *` in Spring, or `0/20 * * * * ?` in Quartz — both 6-field forms that put seconds first, firing at :00, :20 and :40. There is no crontab equivalent: the classic 5-field format begins at minutes, so the tightest single line it can express is `* * * * *`, once a minute.
How do I run a cron job every 20 seconds on Linux?
The crontab workaround is three entries — one plain, then two offset with `sleep 20` and `sleep 40` — landing at :00, :20 and :40. Wrap each in `flock -n` so a run that overruns 20 seconds is skipped rather than stacked. On a systemd distro, a timer with `OnCalendar=*-*-* *:*:00/20` **and `AccuracySec=1s`** is cleaner; `AccuracySec` defaults to 1 minute, and leaving it out lets systemd collapse all three firings into one.
Why doesn't `*/20 * * * *` run every 20 seconds?
Count the fields. That expression has five, and in the 5-field format the leading field is minutes — so it fires every 20 *minutes*, at :00, :20 and :40 past the hour. The version you want has six fields, with seconds in front: `*/20 * * * * *`. Getting this wrong fails silently, because both expressions are valid; the job simply runs 60× less often than intended.
Should I use a 20-second interval to stagger jobs?
It works well for that. Within any minute a 20-second schedule fires at :00, :20 and :40, so it shares an instant with a 30-second job only at :00, and with a 15-second job only at :00. If you have several checks that would otherwise bunch up and spike load together, picking intervals that don't divide into each other spreads them out — 20 alongside 30 collides once a minute instead of twice.
How do I run something every 20 seconds in Spring Boot?
Either `@Scheduled(cron = "*/20 * * * * *")` if you want cron syntax, or `@Scheduled(fixedRate = 20000)` if you just want an interval — the argument is milliseconds and there's no expression to misparse. Switch to `fixedDelay = 20000` when the gap should be measured from the end of the previous run rather than its start, which is what you want if a run can occasionally exceed 20 seconds.
Can Kubernetes CronJob run a job every 20 seconds?
No — CronJob inherits the 5-field format and floors at one minute. Even if it didn't, three pod launches a minute would spend most of their time on scheduling and image pulls rather than your workload. The right shape in Kubernetes is a long-lived Deployment with an internal 20-second ticker.

More seconds schedules

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