Cron schedule
Cron every 15 minutes
The cron expression for every 15 minutes is */15 * * * *. It runs on the quarter hour, at minutes 0, 15, 30 and 45, which is 96 times a day.
| Expression | */15 * * * * |
|---|---|
| Runs | 96 times a day |
| systemd OnCalendar | *:0/15 |
How it works
*/15 takes every fifteenth minute starting from 0, so the job lines up with the quarter hours. The explicit list 0,15,30,45 * * * * is the same schedule and some people find it easier to read.
Quarter-hour alignment is handy for anything people look at on a clock: usage reports, cache warmers before meetings, dashboards that should refresh at predictable times. Restricting the hours is where the schedule gets useful, for example */15 8-18 * * 1-5 during the working week.
Watch out for the six-field schedulers. In node-cron and Spring the first field is seconds, so the familiar string with an extra field means something very different; see the pitfalls.
| Field | Value | Meaning |
|---|---|---|
| Minute | */15 | every 15 minutes: 0, 15, 30, 45 |
| Hour | * | every hour |
| 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
*/15 * * * * /usr/local/bin/job.sh >> /var/log/job.log 2>&1
# Same, but skip a run while the previous one is still going:
*/15 * * * * flock -n /tmp/job.lock /usr/local/bin/job.sh- 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: '*/15 * * * *'
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.
Kubernetes CronJob
apiVersion: batch/v1
kind: CronJob
metadata:
name: scheduled-job
spec:
schedule: "*/15 * * * *"
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": "*/15 * * * *" }
]
}- 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 = ["*/15 * * * *"]- 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 */15 * * * *', 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 15 minutes
[Timer]
OnCalendar=*:0/15
[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/15', 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 */15 * * * *", zone = "UTC")
public void runJob() {
// ...
}
// Quartz CronTrigger:
CronScheduleBuilder.cronSchedule("0 0/15 * * * ?")- 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
15 * * * * is once an hour
A plain 15 in the minute field is a single value: minute 15 of every hour, 24 runs a day. The step needs the slash: */15.
In node-cron and Spring, */15 * * * * * is every 15 seconds
With six fields, the first one is seconds. */15 * * * * * fires four times a minute. For every 15 minutes in a 6-field scheduler, write 0 */15 * * * *.
The last run of a restricted window
*/15 9-17 * * * includes 17:00, 17:15, 17:30 and 17:45, because hour 17 is in the range. If the window should end at 17:00, use */15 9-16 * * * plus a separate 0 17 * * * line.
Frequently asked questions
- What is the cron expression for every 15 minutes?
- */15 * * * *. It runs at minutes 0, 15, 30 and 45 of every hour. 0,15,30,45 * * * * is the same.
- How do I run every 15 minutes on weekdays during business hours?
- */15 9-17 * * 1-5 runs every quarter hour from 09:00 to 17:45, Monday to Friday.
- How do I offset a 15-minute job so it doesn’t run on the quarter hour?
- Use a range with a step: 5-59/15 * * * * runs at minutes 5, 20, 35 and 50.
- What is every 15 minutes in Quartz?
- 0 0/15 * * * ? in Quartz: seconds first, then minutes 0/15, and ? in the day-of-week field.
Last reviewed by Arielton Oberek.