Cron does not mean what you think it means
Seven ways a crontab line does something other than what it reads like: step values that restart, two day fields that mean "or", a percent sign that eats your command.
Five fields and a command: the simplest scheduler there is. It still manages to surprise people who have used it for years. Here are the ones that bite, with the reason behind each.
1. */7 is not "every seven minutes"#
A step starts again at the top of every hour. */7 in the minute field runs at minutes 0, 7, 14 and so on up to 56, then at 0 again, four minutes later. If you need a true seven-minute rhythm, cron is the wrong tool. If you need every 15 minutes, */15 is fine because 15 divides 60.
2. Day of month and day of week mean "or"#
When both fields are restricted, standard cron runs the job when either matches. 0 9 13 * 5 is not "9:00 on Friday the 13th". It is 9:00 on the 13th of every month, and 9:00 on every Friday. If you want the intersection, put the test in the command.
3. A percent sign ends your command#
In a crontab, an unescaped % is turned into a newline, and what follows is fed to the command as input. So date +%F quietly breaks. Write date +\%F, or move the command into a script and call that.
4. The environment is nearly empty#
Cron does not read your shell profile. PATH is short, commonly /usr/bin:/bin, so a command that works in your terminal and says "not found" from cron is the classic. Use absolute paths, or set PATH at the top of the crontab.
5. Output goes somewhere you do not look#
Cron mails the output of a job to the owner if a mail system is set up, and discards it if not. Send output to a file you will read:
*/15 9-17 * * 1-5 /usr/local/bin/sync-data >> /var/log/sync-data.log 2>&16. Runs can overlap#
Cron starts a job on schedule whether or not the previous run has finished. A job that sometimes takes twenty minutes on a fifteen minute schedule will end up running twice. Wrap it in flock so the second one refuses to start:
*/15 * * * * flock -n /tmp/sync-data.lock /usr/local/bin/sync-data7. Time zones and the clock change#
Cron runs on the server's local time. Around a daylight saving change an hour is skipped in spring and repeated in autumn, and different cron implementations treat the jobs scheduled in those hours differently. The simple defence is to keep servers on UTC, or to keep important jobs out of the early hours of local time on those two Sundays. See also a timestamp without a zone is a guess.
Reading one back#
Writing the line is half of it. Reading one somebody else wrote is the other half. That is why cronwtf exists. It prints the line in words and the next five runs, which settles most arguments about what a schedule means:
*/15 9-17 * * 1-5
→ At minute */15 of hour 9-17, on weekdays
Next 5 runs:
2026-10-20 11:45:00 in 14m
2026-10-20 12:00:00 in 29m
2026-10-20 12:15:00 in 44m
2026-10-20 12:30:00 in 59m
2026-10-20 12:45:00 in 1hThe "next runs" were computed at the moment we ran it, so yours will differ. The words will not.
The key is already public: what to do after you commit a secret
Deleting the commit is not the fix. Revoke the key first, check who used it, then clean up and put a scanner in front of the next push.
A timestamp without a zone is a guess
Scraped dates arrive as "2026-03-08 02:30" with nothing else. Some of those times never happened, some happened twice, and the gap between two cities moves during the year.