Skip to content

Cron schedule

Cron every 6 hours

The cron expression for every 6 hours is 0 */6 * * *. It runs four times a day, at 00:00, 06:00, 12:00 and 18:00.

Every 6 hours
Expression0 */6 * * *
Runs4 times a day
systemd OnCalendar*-*-* 0/6:00:00

How it works

*/6 in the hour field expands to 0, 6, 12 and 18, and the fixed 0 minute makes each one a single run on the hour. It’s the same as 0 0,6,12,18 * * *.

The runs are anchored to midnight, not to when you deployed. To use a different set of four times, start the step elsewhere: 0 3-23/6 * * * runs at 03:00, 09:00, 15:00 and 21:00. Or just list the hours you want.

Four times a day is a typical cadence for certificate checks, backups of busy databases and cache rebuilds. On Vercel it needs a Pro plan, since Hobby is limited to one run a day.

Field-by-field breakdown
FieldValueMeaning
Minute0minute 0 (on the hour)
Hour*/6every 6 hours: 0, 6, 12, 18
Day of month*every day of the month
Month*every month
Day of week*every day of the week

Next runs

Scheduler clock

As on GitHub Actions without timezone, Vercel, Cloudflare and Kubernetes with timeZone "Etc/UTC".

Next run times, computed in your browser from the current time
RunYour timeUTC
1Calculating… 
2  
3  
4  
5  

Ready-to-paste versions

The same schedule for each scheduler, with what differs on each one.

crontab (Linux, macOS, BSD)

crontab -e
# m h dom mon dow  command
0 */6 * * * /usr/local/bin/job.sh >> /var/log/job.log 2>&1
  • cron uses the server’s time zone. On cronie (Fedora, RHEL), a CRON_TZ=Europe/London line above the entry changes the zone for the entries below it.

GitHub Actions

.github/workflows/scheduled.yml
on:
  schedule:
    - cron: '0 */6 * * *'
  workflow_dispatch: {}
  • Runs in UTC unless you set timezone, and only on the default branch. GitHub’s docs warn that scheduled runs can be delayed at busy times and that schedules in public repositories are disabled after 60 days without activity.
  • The start of every hour is GitHub’s busiest slot. If the exact minute doesn’t matter, move the 0 to something like 17 to get picked up sooner.

Kubernetes CronJob

cronjob.yaml
apiVersion: batch/v1
kind: CronJob
metadata:
  name: scheduled-job
spec:
  schedule: "0 */6 * * *"
  timeZone: "Etc/UTC"
  concurrencyPolicy: Forbid
  jobTemplate:
    spec:
      template:
        spec:
          restartPolicy: OnFailure
          containers:
            - name: job
              image: busybox:1.36
              command: ["/bin/sh", "-c", "date; echo running"]
  • spec.timeZone is stable since Kubernetes 1.27; without it, the kube-controller-manager’s zone applies. concurrencyPolicy: Forbid skips a run while the previous Job is still active.

Vercel Cron Jobs

vercel.json
{
  "crons": [
    { "path": "/api/cron", "schedule": "0 */6 * * *" }
  ]
}
  • Vercel always uses UTC, doesn’t accept names like MON or JAN, and won’t let you set both day of month and day of week.
  • This runs more than once a day, so it needs a Pro or Enterprise plan. Per Vercel’s docs, a Hobby deployment with an expression like this fails.

Cloudflare Workers

wrangler.toml
[triggers]
crons = ["0 */6 * * *"]
  • Cron Triggers run on UTC. Changes can take up to 15 minutes to propagate across Cloudflare’s network.

node-cron (Node.js)

scheduler.js
import cron from 'node-cron';

// 6 fields: the first one (seconds) is optional
cron.schedule('0 0 */6 * * *', async () => {
  await runJob();
}, { timezone: 'UTC', noOverlap: true });
  • The schedule lives inside the Node process: if it’s down, nothing runs, and three replicas run the job three times. noOverlap skips a tick while the previous run is still going.

systemd timer

job.timer + job.service
# /etc/systemd/system/job.timer
[Unit]
Description=Run job.service every 6 hours

[Timer]
OnCalendar=*-*-* 0/6:00:00

[Install]
WantedBy=timers.target

# /etc/systemd/system/job.service
[Service]
Type=oneshot
ExecStart=/usr/local/bin/job.sh

# systemctl daemon-reload && systemctl enable --now job.timer
  • Check it with systemd-analyze calendar '*-*-* 0/6:00:00', which prints the normalized form and the next elapse. AccuracySec defaults to 1min, so the start can drift by up to a minute.

Spring @Scheduled and Quartz

Java
@Scheduled(cron = "0 0 */6 * * *", zone = "UTC")
public void runJob() {
    // ...
}

// Quartz CronTrigger:
CronScheduleBuilder.cronSchedule("0 0 0/6 * * ?")
  • In Spring, the first of the 6 fields is seconds; the rest follow standard cron (0 or 7 = Sunday).
  • In Quartz, one of the two day fields must be ?, so the day-of-week field gets it.

Pitfalls

Hour steps that don’t divide 24

0 */5 * * * runs at 0, 5, 10, 15 and 20, then 0 again the next day: a 4-hour gap overnight. 0 */7 * * * gives 0, 7, 14, 21 and a 3-hour gap. Only 1, 2, 3, 4, 6, 8 and 12 divide the day evenly.

Which 6 hours depends on the clock’s time zone

In UTC, 00:00/06:00/12:00/18:00 is 21:00/03:00/09:00/15:00 in São Paulo. If the runs should avoid local business hours or peak traffic, pick the hours in the zone the scheduler actually uses.

0 6 * * * is once a day

Without the slash, 6 in the hour field is 06:00 only. The interval needs */6.

Frequently asked questions

What is the cron expression for every 6 hours?
0 */6 * * *, which runs at 00:00, 06:00, 12:00 and 18:00.
How do I run every 6 hours starting at 3 AM?
0 3-23/6 * * * runs at 03:00, 09:00, 15:00 and 21:00. 0 3,9,15,21 * * * is the same.
What is every 6 hours in systemd?
OnCalendar=*-*-* 0/6:00:00, which fires at 00:00, 06:00, 12:00 and 18:00 in the system’s time zone.
How many times a day does 0 */6 * * * run?
Four: 24 hours divided by 6.

Last reviewed by Arielton Oberek.