This is an original planning guide. It is not a vendor manual, an official standard, or legal advice. Example addresses use documentation ranges. Review every command before you paste it.
Cron and timers are calendars, not the job
A schedule only decides when something starts. The job itself is a script or a unit with a working directory, a user, an environment, and a log. People debug the five cron fields for an hour and then discover the script assumed a home directory the cron user does not have. Write the command and the user on the same line as the schedule.
Cron fields are minute, hour, day of month, month, and day of week. A star means every value. 15 2 * * * is 02:15 every day in the host's timezone. Day of month and day of week together do not mean what most people expect: if both are restricted, cron runs when either matches. Prefer one of them starred unless you have tested the combination on this cron.
A systemd timer is a unit that starts another unit. OnCalendar= is the calendar expression. Persistent=true asks systemd to run a missed timer after the host was off, which cron will not do. The timer and the service are two files. Enabling the timer does not enable a forgotten service name. systemctl list-timers shows the next run.
Use cron when the host already runs a pile of crontab lines and the job is a single command whose environment you have made boring. Use a timer when the job should be a service with logs in the journal, a restart policy you do not want, and a calendar that survives a reboot that happened during the slot. Do not run both for the same job.
A daily job at 02:15 that you can prove
The lab job writes one line to a file the service user owns, at 02:15. The cron form is 15 2 * * * and the user is the service account, not root, unless the job truly needs root. The systemd form is OnCalendar=*-*-* 02:15:00 and a service unit whose ExecStart is the same command. Pick one. The example is documentation of a schedule, not a command to install on a production host you have not named.
Timezone is the host's, unless the timer sets one. A laptop that travels and a server set to UTC will not fire at the wall-clock 02:15 you meant. Write the timezone next to the schedule. If the business day is local and the host is UTC, convert the hour in the ticket and show the conversion.
The job must be safe to start twice. A backup that is still running at the next 02:15 should exit because the lock exists, not start a second copy. Cron will start the second copy. A timer will too unless you forbid it. flock or a systemd lock is part of the job, not an optional polish.
The cron explainer and the systemd timer generator on this site turn a sentence into fields. Read the sentence they print back. If it does not match 02:15 every day, do not install the line. After you install it, the proof is a log line from a run, not a file that exists in the right directory.
| Schedule | Cron | OnCalendar | Meaning to confirm |
|---|---|---|---|
| Every day at 02:15 | 15 2 * * * | *-*-* 02:15:00 | Host timezone, not your laptop |
| Mondays at 06:00 | 0 6 * * 1 | Mon *-*-* 06:00:00 | Day-of-week field is 1 or Mon, and you tested it |
| Every 15 minutes | */15 * * * * | *:0/15 | The job finishes well inside 15 minutes |
| Hourly at minute 5 | 5 * * * * | *-*-* *:05:00 | Not at minute 0, when everyone else runs |
A change you can finish
Install the schedule on a lab host or a single named host. Run the job once by hand as the same user. Then let one scheduled run happen, or start the timer once, and keep the log line. A schedule you have not seen fire is a guess.
Remove the other mechanism if this job used to be cron and is now a timer, or the reverse. Two schedules are two incidents.
- Write the user, the working directory, and the command in the ticket.
- Run the command once as that user and keep the exit code.
- Add a lock so a second start exits while the first is running.
- Install either the crontab line or the timer, not both.
- Confirm the next run time in the crontab comment or in list-timers.
- Watch one real run and keep the log line with the hostname and time.
- Disable the schedule in the same window if the run was a test you do not want tomorrow.
Mistakes that outlive the command
scheduling fails in the same few ways. Each one looks finished on the day you type it. The list is here so the ticket can forbid them before the window, not so you can recognise them in an incident review.
- Editing root's crontab for a job that should be a service user.
- Assuming the cron environment has your shell's PATH and your home.
- Using both day-of-month and day-of-week without a test.
- A job that overlaps itself and corrupts the file it was writing.
- Logging only to a file the job cannot create, then concluding the schedule is wrong.
- Persistent timers that surprise you by running a missed backup at boot, on top of the boot storm.
- Leaving the old cron line in place after you added a timer.
- A schedule in local time on a host whose clock is UTC, so the job hits the business day.
Decisions to write down before the window
One decision for scheduling: This job is cron or a timer, and the other one is absent. The ticket names which. The user is the service account. Root is used only when the command cannot run otherwise, and that reason is one sentence. The timezone is written next to the clock time. UTC hosts get a converted hour, not a hope. The job holds a lock. A second start exits. You proved that by starting it twice in the lab. Stdout and stderr go to the journal or to a file the user can create. Silent success is not a requirement. Silent failure is a defect. A missed slot while the host is off is either skipped or caught up, and you chose that on purpose. Persistent=true is the catch-up. Cron is the skip. The command uses a full path or a PATH the crontab sets. It does not depend on an interactive shell rc file. Day-of-month and day-of-week are not both restricted unless you pasted a test result that shows the days you meant. The next run is visible before you leave. list-timers or the crontab is the screen you looked at. And rollback is removing the line or disabling the timer, which you did once in the lab so the command is real.
Schedule checks before the job is allowed to repeat
Confirm this before you call the change done: The user in the schedule matches the user who ran the successful manual test. Only one of cron or a timer is installed for this command. The next run time matches the sentence in the ticket. The timezone or the converted UTC hour is written down. A lock stopped a second overlapping start in the lab. One scheduled run produced a log line you kept. The command does not depend on an interactive PATH. A missed-run choice, skip or catch up, is explicit. The old schedule is gone if this replaced one. Rollback is the disable command you already used once. The job is not scheduled on a host that is about to be rebuilt unless the image contains it. A second person read the minute and hour fields aloud. The command's log line includes a timestamp you can match to the schedule, not only a file that might be old. The hostname in the prompt matches the hostname in the ticket for scheduling. The result line includes the time and the person, not only the command. You can point at the rollback value without searching a chat history. And done is a sentence a second person can repeat: the proof for scheduling happened, and the next host is not started early. If one of those fails, stop.
Tools for this guide
These tools run in the browser on this site. They do not log into your machines.
Related pillar guides
Guides in this cluster
These supporting pages belong to this pillar.