Blog · October 12, 2026

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

On Vercel

On GitHub Actions

2. The most common causes

What happensWhat it usually isHow to fix it
Works by hand, not from cronCron 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 timeThe 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 offIn crontab, % means newline: date +%F never arrives whole.Write it as \%, or move the command into a script.
Stopped after a deploy or a rebootThe 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 silentlyOutput 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 onceIt 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 onceOn 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 neverUnder 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.

If you build it yourself, the watcher can't live on the same server as the cron job: if the server goes down, both go down and nobody alerts you.

5. Get alerted automatically

With Estiaje, you connect the SDK in your script and declare nothing else:

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.

Try Pro free for 14 days No card. Afterwards you stay on Free, with 3 watched processes.

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.

Keep reading