Cron calculators, and the question they cannot answer
Type an expression into any of the dozens of cron calculators on the web and you get a plain-English translation plus the next five run times. That is a genuinely useful thing to have when you inherit a file full of unfamiliar schedules. It is also the answer to a question that has never caused an outage.
Jobs do not break because somebody misread 0 4 * * *. They break because two copies of the same job ran at once, or because forty client sites all started their backup at the same instant, or because the job stopped running and nothing said so. A calculator cannot see any of that, because all three depend on how long the job takes, and the expression does not contain that.
The expressions that remove the need to look anything up
Roughly everything a maintained site needs is in this table. Learn these six shapes and the calculator becomes a thing you use twice a year.
| Expression | When | Reads as |
|---|---|---|
*/5 * * * * | :00, :05, :10 and so on | Every fifth minute of the hour |
17 * * * * | 17 minutes past every hour | Minute seventeen of every hour |
0 4 * * * | 04:00 daily | Minute zero of hour four |
0 4 * * 1 | 04:00 on Mondays | Minute zero of hour four, on Monday |
0 4 1 * * | 04:00 on the 1st | Minute zero of hour four, on day one |
30 2 * * 1-5 | 02:30, weekdays | Minute thirty of hour two, Monday through Friday |
The second row is the one worth adopting as a habit. 17 * * * * instead of 0 * * * * costs nothing, reads the same, and takes your job out of the minute when every other job on the machine starts. More on why that minute is crowded further down.
What the table does not include is anything with a step that does not divide its range evenly, because those are the ones a calculator is genuinely needed for, and needing a calculator to know when your own job runs is the signal to rewrite it. The mechanics of why */40 produces an uneven schedule are in the three traps of a cron expression.
What you are actually pasting into that box
This is the part that gets skipped, and it applies specifically to people who look after other people's servers.
Most calculators take just the five fields. Plenty of them accept a whole crontab and translate every line, which is convenient and is the moment to stop and read what is on the line. A production crontab from a client machine routinely contains the absolute path to the document root, the account name, the database name, the backup destination including a bucket or an offsite hostname, the path to a credentials file, and, more often than anyone would like, an API token sitting in a query string on a curl line.
None of that is secret in the sense of being a password on its own. Together it is a map of the client's infrastructure, submitted to a third party whose retention policy you have not read, from an IP address that identifies your agency. If a client ever asks you to describe your data handling, "we paste production configuration into free web tools" is not a sentence you want to have to say.
Strip the line to its five fields before you paste, or do the whole thing locally. Locally is not harder.
Getting the next run times without leaving the machine
If the box runs cronie, the packaged tool is already there on many distributions:
cronnext -c -n 5
That reads the installed crontabs and prints upcoming times, from the daemon's own parser, on the machine's own clock, in the machine's own timezone. Three properties no website has, and the timezone one matters more than the rest: a calculator renders times in your browser's zone, the daemon runs in the server's, and those differ on nearly every hosting account.
Where cronnext is not available, a scratch virtual environment is a minute of work:
python3 -m venv /tmp/cronenv && /tmp/cronenv/bin/pip -q install croniter
/tmp/cronenv/bin/python - <<'PY'
from croniter import croniter
from datetime import datetime
i = croniter('*/40 * * * *', datetime.now())
for _ in range(6):
print(i.get_next(datetime))
PY
And for a systemd timer, which is a different grammar entirely, the tester ships with the system:
systemd-analyze calendar --iterations=5 '*-*-* 04:00:00'
The number the calculator does not have
Take the run times you just produced and put the job's duration next to them. That comparison is the whole point, and it is the one piece of arithmetic that predicts real failures.
Measure the duration rather than estimating it. Wrap the command:
*/5 * * * * /usr/bin/time -o /var/log/job.time -a /usr/local/bin/sync.sh
Then read the distribution, not the average. A synchronisation job that takes eight seconds on a normal Tuesday and four minutes after a large import is a job scheduled every five minutes that will eventually overlap itself. The average says twelve seconds and tells you nothing.
Three thresholds are worth naming once you have the number:
Duration approaching the interval. Anything past roughly half the interval is a schedule that will collide the first time the work doubles. Either lengthen the interval or make the job refuse to start when a previous copy is still running.
Duration times frequency, as a share of the day. A three-minute job every five minutes is the machine spending most of its life on that job. On a shared plan this is the sort of thing that gets an account throttled without a warning email.
Total runs against any external quota. Every five minutes is 288 runs a day per site. Across forty client sites that is 11 520 calls a day to whatever the job talks to, which is the kind of arithmetic that turns into a rate-limit response nobody was expecting: a 429 your own tooling caused.
Minute zero, and the reason to pick a number at random
Human beings write 0 in the minute field. Panels default to it, documentation examples use it, and generated configuration copies the examples. The result is that on any machine with more than a handful of scheduled jobs, a disproportionate share of them start in the same second.
On one site that is invisible. On a server hosting thirty, the top of the hour is a load spike that makes every one of those jobs slower, which pushes the slow ones towards the next start, which is how overlap begins on a schedule that had plenty of headroom.
The fix is a minute chosen per site and written down, so that the same site always uses the same one and you can find it later. Jenkins made this a language feature with its H operator; plain cron has no equivalent, so it becomes a convention you keep rather than syntax the daemon enforces, which is the same category of thing as agreeing on a PATH line at the top of every crontab: discipline in the part of the line nobody documents.
Before you trust any of the arithmetic above, check that the daemon on the box understands the operator you typed. Step and range syntax that a calculator accepts happily is not guaranteed to survive a move between hosts, and the parts that are not portable are exactly the convenient ones.