Cron job not running? How to find out
Last night's backup, the daily cash report, the reminders to your customers. A cron job that doesn't run doesn't crash: nothing happens, and nobody notices until what it should have done is needed. Here's how to tell whether it ran, the most common causes, and how to find out the same day.
1. Did it run? Where to check
On a Linux server
- Check the right user's crontab:
crontab -las that user, orsudo crontab -l -u deploy. Root's crontab isn't your user's. - See whether cron launched it:
grep CRON /var/log/syslogon Debian and Ubuntu, orjournalctl -u cron. - That tells you it started, not that it finished well. To know how it ended you need the command's output (how to keep it is below).
On Vercel
- In your project, Settings → Cron Jobs lists each job with a link to its logs.
- Cron jobs only run on the production deployment, not on previews.
On GitHub Actions
- In the Actions tab, filter by the
scheduleevent. If there are no recent runs, check whether the workflow was disabled.
2. The most common causes
| What happens | What it usually is | How to fix it |
|---|---|---|
| Works by hand, not from cron | Cron runs with a minimal environment: a short PATH, none of your .env variables, and a different working directory. | Absolute paths (/usr/bin/node), cd into the project on the same line, and load your variables explicitly. |
| Runs, but at the wrong time | The server is on UTC, so 0 2 * * * runs at 2 a.m. UTC, not your local time. GitHub Actions uses UTC too. | Convert the time, or set the zone (on GitHub Actions, the timezone key). |
| The line gets cut off | In crontab, % means newline: date +%F never arrives whole. | Write it as \%, or move the command into a script. |
| Stopped after a deploy or a reboot | The crontab lived on the old server, the cron service didn't start, or your container has no cron. | Keep the crontab in your repository and install it on deploy, or use your platform's scheduler. |
| Runs, but fails silently | Output goes to the server's local mail, which nobody reads, or nowhere. | Send the output to a file: >> /var/log/report.log 2>&1. |
| Sometimes runs twice at once | It takes longer than its interval and the next run starts before the previous one ends. | flock -n so they don't overlap. |
| On Vercel, any minute or only once | On the Hobby plan, cron jobs run once a day at any minute within their hour. A more frequent expression fails at deploy time. | Pro, if you need the exact minute or more than once a day. |
| On GitHub Actions, late or never | Under heavy load, schedule gets delayed and runs can be dropped. It only runs on the default branch, and in public repositories it's disabled after 60 days without activity. | Don't use it for what can't fail, or watch it from outside. |
3. Make it visible
A crontab line that looks after itself: it enters the project, doesn't overlap the previous run, and keeps a record of what happened.
# every 15 minutes, in the project directory, no overlap, with its log
*/15 * * * * cd /srv/store && /usr/bin/flock -n /tmp/reconcile.lock /usr/bin/node scripts/reconcile.js >> /var/log/reconcile.log 2>&1
And have the script say when it started, when it finished and how long it took, and exit non-zero when it fails. Then, when something happens, you'll know what it was.
4. Something has to wait for it from outside
Everything above helps when the cron job runs. But if it doesn't run, it writes nothing: no log, no error, nothing to check. A cron job can't report that it didn't run.
The only way to find out is for something outside to wait for its signal and alert you if it doesn't arrive in time. That's the «heartbeat» idea: the job says «I ran», and another system notices when that message is missing.
5. Get alerted automatically
With Estiaje, you connect the SDK in your script and declare nothing else:
- A daily job is recognized on its own. After 3 days, Estiaje sees that your service runs once a day at the same hour and marks it as a cron job. If nothing has arrived 45 minutes after its hour, your phone gets an alert: «No corrió» (it didn’t run).
- Jobs that run often (every 5 or 15 minutes) are covered by «Callado» (gone quiet): Estiaje learns their rhythm hour by hour and alerts you if they stop sending.
- The one that starts but never finishes: mark the start and the end, and declare that you expect the end.
import { estiajeFromEnv } from '@estiaje/sdk';
const estiaje = estiajeFromEnv({ service: 'daily-report' });
const run = new Date().toISOString().slice(0, 10);
estiaje.event('report.started', { run });
try {
await buildReport();
estiaje.outcome('report.finished', { run });
} catch (err) {
estiaje.outcome('report.failed', { run, reason: err.message });
process.exitCode = 1;
}
await estiaje.flush(); // a script exits right away: wait for delivery
And in the dashboard, the expectation: if report.finished or report.failed doesn't follow report.started in time, you get an alert. A failure your code detected is also counted, separately.
Python, PHP, Ruby or Go? The docs have a recipe for each, including how to wait for delivery before exiting.
Frequently asked questions
Why does my cron job work manually but not from crontab?
Because cron runs with a minimal environment: a short PATH, none of your session or .env variables, and a different working directory. Use absolute paths, cd into the project on the same line, and load your variables explicitly.
What timezone does cron use?
The server's, which is almost always UTC. GitHub Actions also uses UTC unless you set the timezone key.
How do I check whether my cron job ran on Linux?
On Debian and Ubuntu, with grep CRON /var/log/syslog or journalctl -u cron. That shows whether cron launched the command, not whether it finished well: for that, send its output to a log file.
Why isn't my Vercel cron job running?
Vercel cron jobs only run on the production deployment. On the Hobby plan they can only run once a day, at any minute within their hour: one scheduled for 1:00 runs between 1:00 and 1:59.
Sources: the Vercel Cron Jobs documentation (vercel.com/docs/cron-jobs) and GitHub Actions documentation (docs.github.com, the schedule event), checked October 12, 2026.