cron schedule builder

Click together a schedule in plain words and get the systemd timer, the cron line and the next 5 run times.

Last updated

In short: Pick how often a job should run (every few minutes, daily, on chosen weekdays, or monthly on a day, the last day or the first Monday) and this builder writes both formats: a systemd OnCalendar= line such as Fri *-*-* 20:00:00 and a crontab line such as 0 20 * * 5. It also shows the schedule as a sentence and the next 5 run times in your time zone, so you can check it before you save anything.

Your input never leaves your browser. Everything is calculated on your device; nothing you type is sent to a server.

How often should the job run?

On these days
At what time?
Time zone
The job
In words
Every Friday at 20:00
systemd OnCalendar=
Fri *-*-* 20:00:00
Cron line
0 20 * * 5 /usr/local/bin/your-script.sh
Next 5 runs
  1. Calculated in your browser.
  • No command given, so /usr/local/bin/your-script.sh is a placeholder. Replace it with the full path of your script.
  • Cron and the timer both use the server's own time zone. Check it with timedatectl; if it is not Europe/Brussels, tick "Write the time zone into the timer" or the real run times shift by the difference.

Ready-to-use systemd files

1. Save as ~/.config/systemd/user/my-job.timer

[Unit]
Description=Run my-job.service: every Friday at 20:00

[Timer]
OnCalendar=Fri *-*-* 20:00:00
Persistent=true

[Install]
WantedBy=timers.target

2. Save as ~/.config/systemd/user/my-job.service

[Unit]
Description=my-job

[Service]
Type=oneshot
ExecStart=/usr/local/bin/your-script.sh

3. Check the schedule on the machine itself before you enable it:

systemd-analyze calendar --iterations=5 'Fri *-*-* 20:00:00'

systemd-analyze calendar first prints the Normalized form: your expression the way systemd reads it, with every default filled in. If it answers "Failed to parse", the timer would not load either. Then it lists each Iteration in your local time, the same moment in UTC, and how long until then. Those times should match "Next 5 runs" above.

4. Load the files and start the timer (you enable the timer, not the service):

systemctl --user daemon-reload && systemctl --user enable --now my-job.timer

A user timer only runs while you are logged in, unless you run loginctl enable-linger $USER once.

What each part means

The timer file

[Unit]
Section with general information about this unit.
Description=Run my-job.service: every Friday at 20:00
A label shown by systemctl status and systemctl list-timers.
[Timer]
Section that says when the timer fires.
OnCalendar=Fri *-*-* 20:00:00
The schedule: optional weekday, then year-month-day, then hour:minute:second. * means any, .. is a range, /N means every N, ~ counts back from the end of the month.
Persistent=true
If the computer was off at a scheduled time, run the job once as soon as it is back on.
[Install]
Section used by systemctl enable.
WantedBy=timers.target
Start this timer automatically at boot (at login, for a user timer).

The cron line

  • 0 20 * * 5: minute: 0; hour: 20; day of month: any; month: any; day of week: Friday

Prefer cron? Add it with crontab -e (your own jobs) or sudo crontab -e (root's jobs), paste the line at the bottom, save and close. Cron picks it up by itself.

Review generated files before you use them. Provided as-is — see Terms.

How does the cron schedule builder work?

Linux has two common ways to run a job on a schedule. Cron reads one line per job from your crontab (open it with crontab -e): five time fields, then the command. The fields are minute, hour, day of the month, month and day of the week, and * means "any". So 0 20 * * 5 reads as "minute 0 of hour 20, on any date, when it is a Friday". The format is described in crontab(5).

A systemd timer is a small file next to a .service file with the same name. The service says what to run; the timer says when, in an OnCalendar= line that reads like a date with wildcards: weekday year-month-day hour:minute:second. So the same Friday job becomes Fri *-*-* 20:00:00. Ranges use two dots (Mon..Fri, 09..16), /15 means "every 15", and ~ counts back from the end of the month. The grammar is in systemd.time(7), the timer options in systemd.timer(5).

The two formats disagree in one place that trips up many people. When you set both a day of the month and a weekday, systemd needs both to match (Mon *-*-01..07 is the first Monday), but cron runs when either matches (1-7 * 1 is days 1 to 7 plus every Monday). That is why the builder adds a small test in front of the cron command for "first Monday" and "last day" schedules.

Before you rely on a schedule, check it. On the machine itself, systemd-analyze calendar --iterations=5 'Fri *-*-* 20:00:00' prints the next 5 runs, and after enabling a timer, systemctl --user list-timers shows when it fires next. The run times on this page are computed the same way, in your browser.

Worked examples

Every Friday at 20:00

Input Weekly, Friday ticked, time 20:00 gives OnCalendar=Fri *-*-* 20:00:00 cron: 0 20 * * 5 /usr/local/bin/your-script.sh

systemd writes the day name first, then the date (*-*-* means any year, month and day), then the time with seconds. Cron counts the weekday as a number with 0 = Sunday, so Friday is 5.

Every 15 minutes during office hours

Input Every few minutes: 15, only between 09:00 and 17:00, Monday to Friday gives OnCalendar=Mon..Fri *-*-* 09..16:00/15:00 cron: */15 9-16 * * 1-5 /usr/local/bin/your-script.sh

The hour range ends at 16, not 17: hour 16 covers 16:00, 16:15, 16:30 and 16:45, so the last run is at 16:45. Writing 09..17 (or 9-17 in cron) would add runs at 17:00, 17:15, 17:30 and 17:45.

On the last day of every month at 23:00

Input Monthly, the last day, time 23:00 gives OnCalendar=*-*~01 23:00:00 cron: 0 23 28-31 * * [ "$(date -d tomorrow +\%d)" = 01 ] && /usr/local/bin/your-script.sh

In systemd, ~01 means the first day counted back from the end of the month (note: no dash before the ~). Cron has no such field, so the cron line starts on days 28 to 31 and a small test only lets the job through when tomorrow is the 1st.

Frequently asked questions

How do I write a cron job that runs every Friday at 8 pm?

Use 0 20 * * 5 followed by the command: minute 0, hour 20, any day of the month, any month, weekday 5 (Friday). Add it with crontab -e. The systemd timer equivalent is OnCalendar=Fri *-*-* 20:00:00.

How do I run a cron job on the last day of the month?

Cron has no "last day" field. The usual trick is 0 23 28-31 * * [ "$(date -d tomorrow +\%d)" = 01 ] && command: it starts on days 28 to 31 and only continues when tomorrow is the 1st. A systemd timer says it directly with OnCalendar=*-*~01 23:00:00.

How do I run a job on the first Monday of the month?

In systemd: OnCalendar=Mon *-*-01..07 09:00:00, because systemd requires the weekday AND the date to match. In cron, 0 9 1-7 * 1 does not work: when both day fields are set, cron runs on days 1 to 7 OR on any Monday. Use 0 9 1-7 * * with the test [ "$(date +\%u)" = 1 ] && in front of the command instead.

Why does */7 in the minute field not run every 7 minutes?

Because the count restarts every hour: */7 means minutes 0, 7, 14 ... 56, and then 0 again four minutes later. Only steps that divide 60 (1, 2, 3, 4, 5, 6, 10, 12, 15, 20, 30) give an even rhythm, which is why the builder offers only those. The same goes for hours and 24.

Which time zone does my cron job or timer use?

The server's system time zone, which timedatectl shows. A systemd timer can use another zone when you add it at the end, for example OnCalendar=*-*-* 09:00:00 Europe/Brussels. Cron has no time zone field; cronie accepts a CRON_TZ= line, Debian and Ubuntu cron does not.

What happens to a job at 02:30 when the clocks change?

In spring the clock jumps from 02:00 to 03:00 in most of Europe, so 02:30 does not exist that day: systemd skips the run and most cron versions run it right after the jump. In autumn 02:30 happens twice and the job still runs once. Schedule jobs outside 02:00 to 03:00 to avoid the question.

Should I use cron or a systemd timer?

Both are fine for simple jobs. A timer catches up after downtime (Persistent=true), logs to the journal, never starts a second copy while the first is still running, and can spread load with RandomizedDelaySec=. Cron is one line in one file. The builder gives you both, so you can choose.