Cron Expression Builder

Cron Expression for Every 30 Minutes: */30 * * * *

The cron expression */30 * * * * runs at :00 and :30 every hour (48 times per day). Covers */30 vs 0,30 syntax, phase shifting with 15,45, and half hourly use cases.

100% client-side. Your data never leaves your browser.

Minute
Hour
Day of Month
Month
Day of Week

Every 30 minutes

Next 5 Executions
  • 1.Mon, Jun 8, 2026, 23:00
  • 2.Mon, Jun 8, 2026, 23:30
  • 3.Tue, Jun 9, 2026, 00:00
  • 4.Tue, Jun 9, 2026, 00:30
  • 5.Tue, Jun 9, 2026, 01:00
Quick Reference
*Any value
,List separator
-Range
/Step
1-5Range 1 through 5
*/15Every 15 units

Related Tools

The */30 * * * * Expression

*/30 * * * * fires at minute 0 and minute 30 of every hour, totaling 2 runs per hour, 48 per day. The step */30 starts at 0 (the minimum of the minute field) and advances by 30, which yields exactly two values: 0 and 30.

The schedule is as simple as it gets: top of the hour and bottom of the hour, every hour, all day. There is no ambiguity about which minutes are selected and no edge cases about field ranges.

*/30 vs 0,30: Which to Use

Both expressions produce the same schedule. The choice is about intent communication:

*/30 * * * *       # "every 30 minutes"
0,30 * * * *       # "at minute 0 and minute 30"

The explicit 0,30 form is arguably more defensive. It leaves no room for misreading. A developer unfamiliar with step syntax can read 0,30 * * * * correctly on first glance. */30 requires knowing that the step starts at 0 in the minute field.

In code comments or runbooks where the audience includes non cron experts, 0,30 is the safer choice. In a team of engineers comfortable with cron syntax, */30 is fine and is what most people reach for.

Use Cases for Half Hourly Schedules

Digest emails and notifications

Many notification systems batch updates and send summaries on a half hourly cadence. A 30 minute window is long enough to accumulate meaningful content but short enough that users do not feel out of the loop. Twice hourly digests are a common default in alerting and newsletter platforms.

Report generation

Dashboards that show “last 30 minutes” metrics align naturally with a half hourly generation schedule. Running the report at :00 and :30 means the data is always at most 30 minutes stale, which is acceptable for most internal reporting.

Data sync with moderate update frequency

If a source system updates every 15 to 20 minutes, a 30 minute sync means you are at most one update cycle behind. This is often the right tradeoff when the sync involves a nontrivial database write or file transfer.

Caching and index refresh

For search indexes or materialized views that are expensive to rebuild, twice hourly refresh limits the blast radius of a slow rebuild while keeping content reasonably fresh.

Shifting the Phase

The default */30 always fires at :00 and :30. If you want the half hourly job offset from the hour mark, use an explicit start:

15,45 * * * *    # fires at :15 and :45

This is equivalent to 15/30 * * * *, but 15,45 is more readable.

Phase shifted schedules are useful when you have multiple half hourly jobs and want to spread the load:

0,30  * * * *    /opt/jobs/report-generator
15,45 * * * *    /opt/jobs/data-sync
7,37  * * * *    /opt/jobs/cache-warmer

These three jobs are now evenly staggered across the 30 minute window. No two fire at the same minute.

Comparison with @hourly

@hourly (which expands to 0 * * * *) fires 24 times per day. */30 * * * * fires 48 times per day, exactly twice as often.

The choice between hourly and half hourly comes down to your acceptable staleness window. If the job maintains a cache and the underlying data changes faster than every hour, half hourly keeps the cache fresher. If the data changes once per hour at most, @hourly is sufficient and generates half the load.

Neither is always right. Measure how often your data source actually changes before picking the interval.

Running the Job vs @hourly: Side by Side

0 * * * *          # @hourly: 00:00, 01:00, 02:00 ...   (24/day)
0,30 * * * *       # twice-hourly: 00:00, 00:30, 01:00, 01:30 ...  (48/day)

If you upgrade from hourly to half hourly, double check that the job is idempotent or designed for twice as frequent execution. Some jobs (like sending a summary email) should only fire once per trigger, and running twice per hour would double the emails. Others (like a cache refresh) are perfectly safe to run more frequently.

Verifying the Schedule

from croniter import croniter
from datetime import datetime

cron = croniter("*/30 * * * *", datetime.now())
for _ in range(6):
    print(cron.get_next(datetime))

This prints the next 6 execution times, covering three hours of data, showing the :00/:30 pattern clearly. Compare it against 15,45 * * * * to see how a phase shift affects the output.

To confirm the job is running on a live system:

journalctl -u cron --since "3 hours ago" | grep myjob | wc -l

Over 3 hours you should see exactly 6 entries. Fewer suggests the job is failing or exiting without output; more suggests duplicate entries in the crontab.