epochkit

Cron expression for twice a day

The cron expression 0 0,12 * * * runs at 12:00 AM and 12:00 PM. The restricted fields are minute 0 and hour 0,12; the remaining asterisks match anything. That works out to 2 runs per day.

0 0,12 * * *

Field breakdown

Field-by-field meaning of the cron expression 0 0,12 * * *
Field Value Meaning
Minute 0 The top of the hour (:00)
Hour 0,12 00:00 (midnight) and 12:00 (noon)
Day of month * Every day of the month (1–31)
Month * Every month (1–12)
Day of week * Every day of the week (0–6)

Next runs

Next 5 runs Timezone: UTC

Run times are calculated in your browser timezone. The scheduler that executes the job uses its own — always UTC on Vercel Cron, UTC by default on GitHub Actions, the host clock on crontab.

How this schedule works

Cron evaluates the five fields of 0 0,12 * * * — minute, hour, day of month, month, day of week — against the clock once a minute and starts the job on any minute where all five match, which cronstrue reads as "At 12:00 AM and 12:00 PM": 2 runs per day. The runs land at 00:00, 12:00, because a comma-separated list matches each value it names and nothing between them.

When to use twice a day

Two runs a day cover the large middle ground between hourly polling and a single nightly batch: a morning and an evening sync, a twelve-hour backup rotation, digests at the start and the middle of the day. Writing the hours as a list rather than a step is the whole point — it lets the two runs sit wherever they are actually useful, twelve hours apart or not, and it states the times plainly for whoever reads the crontab after you. Reach for it whenever one daily run leaves too large a gap.

Common mistakes with twice a day

Two runs a day invite an assumption that rarely holds: that they are equivalent. The midnight run usually processes a full, quiet, closed period; the noon run processes half a day against live traffic. Sizing and timeouts derived from one will not necessarily hold for the other. The expression-level mistake is a stray space — 0 0, 12 * * * is not a valid five-field expression, and cron's error reporting for it is famously unhelpful. Keep lists tight, with no spaces around the commas. And note that Vercel Cron rejects this schedule outright on the Hobby plan, which allows one run per day.

Platform snippets

The same expression, ready to paste into the four schedulers that read five-field cron. The notes below each snippet are the ones that follow from this schedule; the cron expression generator lists every caveat for every platform.

GitHub Actions

yaml
name: Scheduled workflow
on:
  schedule:
    - cron: '0 0,12 * * *'
  workflow_dispatch:

jobs:
  scheduled:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Run job
        run: echo "Running scheduled job"
  • • Workflows scheduled at the top of the hour may be delayed during high-load periods. Consider offsetting the minute field (e.g. "5 0,12 * * *") for more reliable timing.

Kubernetes CronJob

yaml
apiVersion: batch/v1
kind: CronJob
metadata:
  name: my-cron-job
spec:
  schedule: "0 0,12 * * *"
  jobTemplate:
    spec:
      template:
        spec:
          containers:
            - name: job
              image: my-image:latest
              command: ["/bin/sh", "-c", "echo Running"]
          restartPolicy: OnFailure

Vercel Cron

json

Hobby plan limitation: cron jobs can only run once per day. Schedules more frequent than daily will fail to deploy on Hobby. Use Pro plan or above for higher frequency.

{
  "$schema": "https://openapi.vercel.sh/vercel.json",
  "crons": [
    {
      "path": "/api/cron",
      "schedule": "0 0,12 * * *"
    }
  ]
}
  • • Pro plan and above: invocations occur within the specified minute (e.g. "5 8 * * *" runs between 08:05:00 and 08:05:59).

crontab

bash
# Edit your crontab: crontab -e
0 0,12 * * * /path/to/command

# With logging:
0 0,12 * * * /path/to/command >> /var/log/cron.log 2>&1

Variations

Frequently asked questions

How do I read the cron expression 0 0,12 * * *?

Read the five space-separated fields left to right as minute, hour, day of month, month, and day of week. In 0 0,12 * * * the minute is 0, hour is 0,12, day of month is *, month is *, day of week is *. Of the operators cron defines, this expression uses only the ones that matter here: an asterisk matches every value in its field and a comma builds a list of individual values. Put together, cronstrue reads the whole thing as "At 12:00 AM and 12:00 PM", and a scheduler that checks all five fields once a minute will start the job 2 runs per day.

What timezone does 0 0,12 * * * run in?

The schedule repeats through the day at fixed hours — 00:00, 12:00 — and which part of the day those hours fall in depends entirely on the scheduler. Moving between zones does not change how often the job runs; it changes when. GitHub Actions defaults to UTC and takes an optional timezone key next to cron, so the hours can be anchored to an IANA zone. Vercel Cron always evaluates in UTC, with no setting to change it. Kubernetes CronJobs use the controller's local zone unless spec.timeZone is set, and a CRON_TZ prefix inside the schedule string is rejected outright. A crontab follows the host clock unless CRON_TZ overrides it. In any zone that observes daylight saving, the whole set of run hours shifts by one twice a year.

How do I run a cron job twice a day at custom hours?

Put both hours in the hour field as a comma-separated list and leave the minute pinned: 0 0,12 * * * runs at midnight and noon, and 0 9,17 * * * runs at 09:00 and 17:00. A list accepts any hours, so the two runs do not have to be twelve apart — 0 8,14 * * * is equally valid. Reach for a step such as */12 only when the interval divides 24 evenly and starting at hour 0 is acceptable. Lists are the portable choice: every scheduler that reads five-field cron supports them.

Related cron schedules

Build a custom schedule in the cron expression generator, or browse every epochkit tool.