Skip to content
SERVER TOOL

Crontab checker: whole file, timezone, next run

The whole file at once, not one line at a time. With a warning when several jobs sit on the same minute.

Tool

Nobody enjoys reading a crontab that grew over the years. Five stars here, a slash there, and behind it a command somebody else wrote three years ago. Paste the whole file and every line comes back in plain words: what it does, when it runs next, and where it gets in another job's way.

01: How to Use

How do you use this tool?

  1. Run crontab -l on the server and copy the complete output, including comments, blank lines and assignments such as CRON_TZ or MAILTO.
  2. Pick at the top whether this is a personal crontab or a file from /etc/crontab with its user column, then set the timezone you think in.
  3. Paste everything into the large field. The review appears immediately, line by line, with a plain-language reading and the next run.
  4. Read the collision block. If an instant carries several jobs, hit compute proposal and copy the de-clustered file.
  5. To hand the result on, take the report as Markdown at the bottom, or as JSON when a script or an assistant should work with it.

What does the crontab checker do?

It takes what cron on your server actually reads: the whole file. Comment lines, blank lines, assignments such as CRON_TZ=, PATH= and MAILTO=, the schedule and the command behind it. None of that has to be stripped out first. Every line in becomes a line out: the schedule in plain words, the next run in the zone that applies to that particular line, and the notes that belong to it.

The checker runs entirely in your browser. No upload, no server, no cookie wall. With a crontab that is not a side issue, because the file names hosts, script paths, database names and now and then a password somebody typed onto a command line because it had to be quick.

Why is a single-expression checker not enough?

Because the interesting mistakes live between the lines. One expression can be looked up in seconds, and there are good tools for it. None of them answers the questions you actually have in front of an inherited crontab: is the same thing scheduled twice? Does the backup collide with the export? Does the CRON_TZ at the top still cover the line at the bottom? And why has no mail arrived since the server move?

Those are properties of the file, not of the expression. Copying a crontab into a single-line field one row at a time shows you every row in isolation, and therefore none of these questions. The checker turns the order around: the file first, then the line.

What happens when several jobs start on the same minute?

They share everything. Processor, disk, network and usually the same database. The classic case looks like this:

0 0 * * * /usr/local/bin/backup-db.sh
0 0 * * * /usr/local/bin/backup-files.sh
@daily    /usr/local/bin/rotate-logs.sh
0 0 * * 1 /usr/local/bin/weekly-report.sh
0 0 * * * /opt/app/bin/reindex.sh

Five lines, four of them daily, one weekly. On Monday at midnight all five start inside the same minute. On paper that is a schedule; in practice it is a load spike. And because @daily expands to nothing other than 0 0 * * *, skimming the file does not reveal the collision at all.

The checker computes the upcoming runs of every job and lays them side by side. When more than one job lands on the same instant, it says so, with the actual date, the line numbers involved and the worst offender where the most jobs start together. Because it compares when things run rather than how they are written, @daily against 0 0 * * * and the weekly job against the daily ones both show up.

How does the tool spread the start times out?

At the press of a button, without touching the rest of the file. The first job of each pile-up keeps its minute, the others move evenly across the hour. The five lines above become minute 0, 15, 35, 12 and 5, still at midnight, still on the same day, with the same command. Hour, day and month stay untouched.

The proposal then checks itself. The rewritten file is computed through again from scratch. If an instant is left where two jobs still coincide, they are moved further until nothing collides. Only then does the panel say it worked out. Lines whose minute field is a list or a step are not moved automatically, because shifting them would change their meaning; they are named individually for you to look at instead.

The result is the complete file, ready to copy, with every comment and assignment exactly where it was. It goes straight back into crontab -e.

Which timezone applies to a cron job?

The system’s, as long as nothing says otherwise. Not UTC, not the browser’s, and not the one the person who wrote the line had in mind. Getting that wrong gives you a job that is supposed to run at six and in fact runs at midnight, with no log entry hinting at it.

A line reading CRON_TZ=Europe/Berlin changes it, but only for the lines below. Put it in the middle of the file and the jobs above it keep running in the system zone. The checker implements exactly that: it remembers the most recent zone as it walks down the file and computes each line in it. Lines in a different zone carry that zone visibly, and the overview counts how many distinct zones a file contains. Two is nearly always an accident.

Then there is daylight saving. When the clock jumps forward, a job inside the skipped hour does not run at all. When it goes back, it runs twice. Both are documented behaviour rather than bugs, but hardly anybody knows it before the first duplicate billing run.

Why can cron not find my script?

Because cron starts in a different world from your login session. No .bashrc, no .profile, none of the environment your deployment sets, and a PATH that per crontab(5) is usually nothing but /usr/bin and /bin. Everything a version manager, a package manager or your own build put into /usr/local/bin or the home directory is out of reach.

The checker flags every command written without a path that is not part of that base set. tar and find get no note; a hand-written cleanup does. If a PATH= sits at the top of the file, the note stays quiet, because the condition is then met.

A second classic lives in the command itself: the percent sign. In a crontab % is not an ordinary character. cron cuts the line there and pushes the remainder to the job on standard input. tar -czf backup-%Y.tgz becomes tar -czf backup- and a file nobody goes looking for. A backslash in front fixes it. The checker finds the spot and counts the backslashes correctly, so an already escaped percent sign raises no false alarm.

Where does a cron job’s output go?

Into mail, unless you say otherwise. If a job produces output and it is not redirected, cron sends it to the owner of the crontab or to the address in MAILTO. On servers without working mail delivery it therefore vanishes, and nobody notices.

The checker separates three states, because their consequences differ sharply. No redirect and no MAILTO: every run sends mail. MAILTO="" set: the output is thrown away, error messages included. Redirected to > /dev/null 2>&1: everything quiet, but deliberately so. A lone 2>/dev/null does not count as a redirect, because it only silences the errors and leaves the actual output flowing, which is the exact opposite of what was intended.

What is the difference between crontab -l and /etc/crontab?

The user column. A personal crontab has five time fields and then the command directly. The system-wide files /etc/crontab and everything under /etc/cron.d insert a column with the user name the job should run as, between the schedule and the command.

Copying a line from one into the other produces a failure that stays silent. In a personal crontab root simply becomes the command, and cron tries to start a program called root. That is why you pick the kind of file at the top. In personal mode the checker points out a line that looks suspiciously like it still carries a user column.

What does this tool not check?

It cannot see your server. Whether a script exists, whether it is executable, whether the user has the rights and whether the service is running at all is nothing anybody can answer from here. The checker works with the text you paste and nothing else.

It does not watch executions either. If you want to know whether a job really ran last night, you need monitoring on the server, not a tool in a browser. And it does not stop one run from overtaking the next: a lock file in the script does that. Here you only get the note that a job starts often enough for it to happen.

For single expressions, for Quartz, for AWS EventBridge and for exporting to GitHub Actions or a Kubernetes CronJob there is the sibling tool, the cron expression builder. Checking a file belongs here. Building a line belongs there.

Last updated:

You might also like