Skip to content
Get started

How to monitor a Vercel Cron job

To monitor a Vercel Cron job, have the cron route report success to an external monitor at the end of its work and alert when that report doesn't arrive in the expected window. This matters because Vercel Cron has plan-specific behavior: on the Hobby plan crons are limited to running once per day, and Vercel may invoke the job at any point within the scheduled hour rather than at the exact minute, so your monitor's grace window needs to account for that imprecision.

What's different about Vercel Cron scheduling?

  • Hobby plan runs once per day: on the Hobby plan, cron expressions are limited to a daily cadence. A more frequent expression (hourly, every 30 minutes) fails at deploy time. Pro and Enterprise plans allow more frequent schedules.
  • Imprecise invocation on Hobby: Vercel may invoke a Hobby cron at any time within the scheduled hour to spread load — an expression for 08:00 can fire anywhere between 08:00 and 08:59. Pro/Enterprise invoke within the specified minute.
  • It's an HTTP invocation: a Vercel Cron hits a route in your app. If that route throws, times out, or the deployment is broken, the scheduled work fails — and Vercel doesn't alert you that the run produced an error.

How do I add a heartbeat to a Vercel Cron route?

Define the schedule in vercel.json, then ping the monitor at the end of the cron route's handler after the work succeeds:

vercel.json
{
  "crons": [
    { "path": "/api/cron/nightly", "schedule": "0 3 * * *" }
  ]
}
app/api/cron/nightly/route.ts
export async function GET() {
  try {
    await runNightlyJob();
    // Report success last. If the work throws, this is skipped and the monitor alerts.
    await fetch("https://ping.cronshield.com/<your-check-id>", { signal: AbortSignal.timeout(10000) });
    return new Response("ok");
  } catch (err) {
    await fetch("https://ping.cronshield.com/<your-check-id>?fail=1").catch(() => {});
    return new Response("failed", { status: 500 });
  }
}
On the Hobby plan, set the monitor's grace period wide enough to absorb the up-to-an-hour invocation window so you don't get a false miss. PING_URL is a placeholder for the endpoint you get on a monitor.

How do I know if the cron never fired at all?

A broken deployment, a removed cron entry, or a plan change can stop the invocation entirely, and Vercel won't email you about a run that never happened. The heartbeat covers this: when the expected success ping doesn't arrive in its window, the monitor alerts — whether the route errored or was never invoked. On paid tiers, CronShield adds the failing route's last log line and a likely cause to the alert.

Add a missed-run alert to this job

The free tier gives you a heartbeat endpoint and an email alert when an expected ping doesn't arrive. Paid tiers add the log-aware diagnosis — the last log line and a likely cause in the alert. The heartbeat receiver ships in an upcoming release; see the plans to learn what each tier adds.

Frequently asked questions

Can I run a Vercel Cron job every minute on the Hobby plan?
No. On the Hobby plan, cron jobs are limited to a daily schedule, and a more frequent expression fails to deploy. To run more often you need the Pro or Enterprise plan, or an external scheduler that calls your route.
Why did my Vercel Cron run at a different time than scheduled?
On the Hobby plan, Vercel may invoke a cron at any point within the scheduled hour to distribute load, so an 08:00 job can run anywhere in the 08:00 hour. Pro and Enterprise invoke within the specified minute. Widen your monitor's grace window on Hobby to avoid false misses.