There's a particular kind of frustration in a cron job that won't run: it works perfectly when you type the command by hand, then does nothing at the scheduled time. No error, no output, no clue. The script is fine — the problem is almost always the gap between the comfortable environment you run things in and the bare, stripped-down world cron actually lives in. Once you understand that gap, debugging a silent cron job stops being guesswork and becomes a short, reliable checklist.
Check the schedule first
Before you suspect anything clever, suspect the schedule — it's the most common culprit by a wide margin. Paste your cron expression into a parser and confirm it actually means what you think it does; “0 0 * * 0” is weekly on Sunday, not daily, and that single misreading accounts for a surprising number of “it didn't run” reports.
Then check two things people forget. Cron uses the server's timezone, which is often UTC rather than your local time, so a job you expect at 9am may be firing at what you think of as the middle of the night. And confirm the cron service itself is even running — a quick systemctl status cron rules out the embarrassing case where the whole scheduler is stopped.
Paths, permissions and environment
Here's the heart of it: cron does not run with your shell. It runs with a minimal environment that has almost none of the conveniences you take for granted, which is why a script that works flawlessly by hand can do nothing under cron.
The usual offenders are all variations on that theme. The script calls a command that isn't on cron's bare PATH. It uses a relative path that only resolves from your working directory. It isn't marked executable. Or it quietly depends on environment variables set in your .bashrc — database URLs, API keys — that cron never loads. The fixes are equally consistent: use absolute paths everywhere, set PATH explicitly at the top of the script, and source any environment you need rather than assuming it's there.
Read the logs
Stop guessing and let the system tell you what happened. Cron logs to the system log, so check /var/log/syslog or journalctl -u cron to answer the first question: did the job fire at all? That one answer splits the problem cleanly in two.
If there's no log line at the expected time, the issue is upstream — the schedule or the cron service — not your script, so go back to step one. If it did fire but the work didn't happen, the script ran and failed silently. Redirect its own output to a file with >> /path/to/log 2>&1 so the next run captures the actual error instead of throwing it away. That redirect turns an invisible failure into a readable one.
How to know the moment it fails with WatchControl
Even after you've fixed today's problem, there's a deeper one the checklist can't solve: a cron job that has run perfectly for months will eventually fail silently anyway. A server crashes, a deploy changes a path, a disk fills up — and because nothing polls a cron job, nothing tells you. You find out when you need the result that isn't there.
Heartbeat monitoring is the answer to that, and it's the one piece that protects you going forward rather than just explaining the past. Your job pings a unique URL when it finishes successfully; if that ping doesn't arrive on schedule, you're alerted. In WatchControl you create a heartbeat monitor and add a single curl line to the end of your job — no agent, free to start — and from then on a cron job that stops becomes a notification, not a nasty surprise.