Cron schedule
Cron every week
To run a cron job once a week, use @weekly or its expansion 0 0 * * 0, which fires at 00:00 every Sunday. Any single weekday works: 0 0 * * 1 is weekly on Mondays.
| Expression | 0 0 * * 0 |
|---|---|
| Macro | @weekly |
| Runs | 52 times in 2027 |
| systemd OnCalendar | Sun *-*-* 00:00:00 |
How it works
crontab(5) defines @weekly as 0 0 * * 0, and the Kubernetes CronJob docs list the same expansion. Spring’s @weekly is the 6-field equivalent 0 0 0 * * 0. GitHub Actions explicitly doesn’t support @weekly, and Vercel and Cloudflare document only the five fields, so write them out.
The weekday is a choice, not a rule. Sunday is only the default because of how @weekly was defined. systemd picked Monday for its weekly calendar keyword, and anacron’s weekly jobs run once every 7 days counted from the last run, not on a fixed weekday.
Weekly also means you have a week to notice a broken job. Log the run, alert on failure, or add a heartbeat check that complains if the job hasn’t reported in 8 days.
| Field | Value | Meaning |
|---|---|---|
| Minute | 0 | minute 0 (on the hour) |
| Hour | 0 | hour 0 (12 AM) |
| Day of month | * | every day of the month |
| Month | * | every month |
| Day of week | 0 | Sunday |
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 0 * * 0 /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 0 * * 0'
# timezone: 'America/New_York' # optional; UTC when omitted
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. - With a
timezonethat observes DST, a time skipped by the spring-forward change advances to the next valid time (the docs’ example: 2:30 AM becomes 3:00 AM).
Kubernetes CronJob
apiVersion: batch/v1
kind: CronJob
metadata:
name: scheduled-job
spec:
schedule: "0 0 * * 0"
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 0 * * 0" }
]
}- 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 fits the Hobby plan, but Hobby precision is per hour: the invocation can land anywhere within the scheduled hour.
Cloudflare Workers
[triggers]
crons = ["0 0 * * SUN"]- Cron Triggers run on UTC. Changes can take up to 15 minutes to propagate across Cloudflare’s network.
- Cloudflare numbers weekdays 1 = Sunday to 7 = Saturday, unlike standard cron, so the example uses names (
MON,SUN), which are unambiguous.
node-cron (Node.js)
import cron from 'node-cron';
// 6 fields: the first one (seconds) is optional
cron.schedule('0 0 0 * * 0', 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 weekly, Sunday at midnight
[Timer]
OnCalendar=Sun *-*-* 00:00:00
Persistent=true
[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 'Sun *-*-* 00:00:00', which prints the normalized form and the next elapse.AccuracySecdefaults to 1min, so the start can drift by up to a minute. Persistent=trueruns the job at boot if the machine was off at the scheduled time, which plain cron doesn’t do.- systemd’s own
OnCalendar=weeklymeansMon *-*-* 00:00:00, Monday rather than Sunday. The unit above spells out Sunday to match cron’s@weekly.
Spring @Scheduled and Quartz
@Scheduled(cron = "0 0 0 * * 0", zone = "UTC")
public void runJob() {
// ...
}
// Quartz CronTrigger:
CronScheduleBuilder.cronSchedule("0 0 0 ? * SUN")- 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
?, and weekdays run 1 = Sunday to 7 = Saturday, which is why the example uses names.
Pitfalls
systemd weekly is Monday
systemd-analyze calendar weekly normalizes to Mon *-*-* 00:00:00. Moving a @weekly crontab entry to a timer with OnCalendar=weekly shifts it by a day.
Every two weeks can’t be written in cron
There is no week-number field. Run weekly and test the ISO week: [ $(( $(date +\%V) \% 2 )) -eq 0 ] && job.sh. Years with 53 ISO weeks put week 53 and week 1 back to back, both odd, so the rhythm breaks there: an even-week job waits three weeks, an odd-week job runs two weeks in a row.
Weekly runs on a machine that is off
If the server is down at Sunday 00:00 the run is lost until next week. anacron or systemd Persistent=true will catch up at boot.
Frequently asked questions
- What is the cron expression for once a week?
- 0 0 * * 0, or @weekly, runs at midnight every Sunday. Change the last field to pick another day.
- What day does @weekly run?
- Sunday at 00:00 in crontab, Kubernetes and Spring. systemd’s weekly keyword is Monday at 00:00.
- How do I run a cron job every two weeks?
- Schedule it weekly and let the command check the week number, for example [ $(( $(date +\%V) \% 2 )) -eq 0 ] && job.sh.
- Is every 7 days the same as every week?
- In cron, yes, as long as you schedule by weekday. */7 in the day-of-month field is not the same: it runs on days 1, 8, 15, 22 and 29 and resets every month.
Last reviewed by Arielton Oberek.