Cron schedule
Cron every 12 hours
The cron expression for every 12 hours is 0 */12 * * *. It runs twice a day, at 00:00 and 12:00, and is identical to 0 0,12 * * *.
| Expression | 0 */12 * * * |
|---|---|
| Runs | 2 times a day |
| systemd OnCalendar | *-*-* 0/12:00:00 |
How it works
*/12 over the hour range 0-23 yields exactly two values, 0 and 12. With the minute fixed at 0 that’s midnight and noon. Many people find the list form 0 0,12 * * * clearer because it names the two hours outright.
The step can’t be anchored to anything but midnight, so if you need 12-hour spacing at other times, write the two hours yourself: 0 6,18 * * * or 30 8,20 * * *. Any pair of hours 12 apart keeps the gap even.
For jobs that must run twice a day in local time, remember the hosted schedulers are UTC. 00:00 and 12:00 UTC are 21:00 and 09:00 in São Paulo, and 19:00/07:00 or 20:00/08:00 in New York depending on DST.
| Field | Value | Meaning |
|---|---|---|
| Minute | 0 | minute 0 (on the hour) |
| Hour | */12 | every 12 hours: 0, 12 |
| Day of month | * | every day of the month |
| Month | * | every month |
| Day of week | * | every day of the week |
Next runs
As on GitHub Actions without timezone, Vercel, Cloudflare and Kubernetes with timeZone "Etc/UTC".
| Run | Your time | UTC |
|---|---|---|
| 1 | Calculating… | |
| 2 | ||
| 3 | ||
| 4 | ||
| 5 |
Ready-to-paste versions
The same schedule for each scheduler, with what differs on each one.
crontab (Linux, macOS, BSD)
# m h dom mon dow command
0 */12 * * * /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/Londonline above the entry changes the zone for the entries below it.
GitHub Actions
on:
schedule:
- cron: '0 */12 * * *'
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
0to something like17to get picked up sooner.
Kubernetes CronJob
apiVersion: batch/v1
kind: CronJob
metadata:
name: scheduled-job
spec:
schedule: "0 */12 * * *"
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.timeZoneis stable since Kubernetes 1.27; without it, the kube-controller-manager’s zone applies.concurrencyPolicy: Forbidskips a run while the previous Job is still active.
Vercel Cron Jobs
{
"crons": [
{ "path": "/api/cron", "schedule": "0 */12 * * *" }
]
}- Vercel always uses UTC, doesn’t accept names like
MONorJAN, 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
[triggers]
crons = ["0 */12 * * *"]- Cron Triggers run on UTC. Changes can take up to 15 minutes to propagate across Cloudflare’s network.
node-cron (Node.js)
import cron from 'node-cron';
// 6 fields: the first one (seconds) is optional
cron.schedule('0 0 */12 * * *', 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.
noOverlapskips a tick while the previous run is still going.
systemd timer
# /etc/systemd/system/job.timer
[Unit]
Description=Run job.service every 12 hours
[Timer]
OnCalendar=*-*-* 0/12: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/12:00:00', which prints the normalized form and the next elapse.AccuracySecdefaults to 1min, so the start can drift by up to a minute.
Spring @Scheduled and Quartz
@Scheduled(cron = "0 0 */12 * * *", zone = "UTC")
public void runJob() {
// ...
}
// Quartz CronTrigger:
CronScheduleBuilder.cronSchedule("0 0 0/12 * * ?")- 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
0 12 * * * is once a day
A plain 12 in the hour field is noon only. The every-12-hours schedule needs the step */12 or the list 0,12.
0 */13 * * * is not every 13 hours
It matches hours 0 and 13, so the gaps alternate between 13 and 11 hours. Intervals that don’t divide 24 need an epoch check in the command, as in the crontab(5) notes: run hourly and test $(( $(date +\%s) / 3600 \% 13 )).
A missed run waits 12 hours
If the machine is off at 00:00, cron doesn’t catch up; the next run is at noon. systemd’s Persistent=true or anacron runs the missed job at boot.
Frequently asked questions
- What is the cron expression for every 12 hours?
- 0 */12 * * *, which runs at 00:00 and 12:00. 0 0,12 * * * is the same schedule.
- How do I run every 12 hours at 6 AM and 6 PM?
- List the hours: 0 6,18 * * *.
- Is every 12 hours the same as twice a day?
- Every 12 hours is one specific case of twice a day: the two runs are exactly 12 hours apart. 0 9,17 * * * is also twice a day, but with uneven gaps.
- What is every 12 hours in Kubernetes?
- spec.schedule: "0 */12 * * *", ideally with spec.timeZone set so the two runs land at the local times you expect.
Last reviewed by Arielton Oberek.