Skip to content
LastPing
GUIDE · VERCEL · CRON JOBS

Vercel cron job not running: what Vercel skips, and how to hear about it

Vercel runs the cron, but nothing tells you when it did not: a run that was never delivered leaves no log, and a failed one is not retried. Most causes are documented by Vercel and quick to check. Work through them below, then add three pings to the route, so a missed, failed or timed-out run opens an incident.

Your assistant can do this for you

Find out why my Vercel cron job is not running, and monitor it with LastPing

Vercel's documented behaviour: "Cron job delivery is best effort." When a request does not reach your function, "your function does not execute, and no runtime log is created for that scheduled run." And: "Vercel will not retry an invocation if a cron job fails." (Vercel documentation, managing cron jobs.)
On this page
  1. Check these first
  2. Ten reasons a Vercel cron job does not run
  3. Get alerted when a cron run is missed or fails
  4. What this catches
  5. Questions people ask

Check these first

Two places tell you most of it: the project's Cron Jobs settings and the runtime logs behind them.

  • Settings → Cron Jobs: is the list there, and is Disable Cron Jobs off?
  • View Logs next to a cron job opens the runtime logs filtered to its path. No entry at all points to the deployment, the plan, a redirect, a cache or a delivery that never happened. An entry with 401, 404 or 500 points to the route.
  • The CLI lists the project's crons and can trigger one on demand: vercel crons.

Ten reasons a Vercel cron job does not run

In the order worth checking, quoting Vercel's documentation where it is exact.

The deployment is not Production

Vercel triggers a cron job with an HTTP GET request to the project's production deployment URL, and its troubleshooting guide says it plainly: "Cron jobs only run on Production deployments." A cron added on a preview deployment does nothing until it reaches Production.

Hobby runs once a day, somewhere in the hour

On the Hobby plan a cron job can run once per day, and an expression that runs more often fails the deployment with Hobby accounts are limited to daily cron jobs. This cron expression would run more than once per day. Timing is per hour: 0 1 * * * can run any time between 1:00 and 1:59. Pro and Enterprise run once per minute at most, within the minute specified. Every plan allows 100 cron jobs per project.

The time is UTC

"The timezone is always UTC." Vercel also rejects names such as MON or JAN, and day of month and day of week cannot both be set: when one has a value, the other must be *. The cron expression explainer lists the next runs of an expression in UTC and in your own zone.

Cron jobs are disabled for the project

Settings → Cron Jobs → Disable Cron Jobs applies to the whole project, persists across deployment promotions and Instant Rollbacks, and stays off until someone re-enables it. Disabled cron jobs are still listed, so the list looks normal.

The route redirects

"Cron jobs do not follow redirects." A 3xx response ends the invocation, and the redirect does not show in the logs. With trailingSlash enabled, the cron path needs its trailing slash.

The response comes from a cache

A cached response also leaves no log entry, and your code never runs. In a Next.js route handler, Vercel's troubleshooting guide suggests export const dynamic = 'force-dynamic'.

The request is refused before your code runs

With CRON_SECRET set, Vercel sends it as Authorization: Bearer <secret>. A secret with a newline or characters that cannot go in a header fails that check, so your route answers 401 every time. Custom firewall rules can block the request as well.

The path or the build does not match

A cron for a path that does not exist still runs and gets a 404. If a route was renamed, the path in vercel.json must follow it. To confirm the crons were registered, run vercel build --prod and look for the crons property in .vercel/output/config.json.

The function runs out of time

Cron jobs have the same duration limits as Vercel Functions, set with maxDuration. A job that needs longer is stopped part-way, and Vercel does not retry it: "Vercel will not retry an invocation if a cron job fails."

The run was never delivered

Vercel calls cron delivery best effort: "occasional transient network errors can prevent a request from reaching your function. In those cases, your function does not execute, and no runtime log is created for that scheduled run." The same scheduled run can also arrive twice, so make the job idempotent.

Get alerted when a cron run is missed or fails

Vercel does not tell you when a scheduled run never arrived, and it does not retry a failed one. A ping from the route, checked against the schedule outside Vercel, covers both.

Create a monitor with the cron's schedule

In the console, create a monitor with the same expression as the vercel.json entry, timezone UTC. On Hobby, make the grace window longer than an hour, because the run can come any time in its hour. Copy the ping URL into a LASTPING_URL environment variable on the project.

{
  "crons": [
    { "path": "/api/cron/nightly", "schedule": "0 3 * * *" }
  ]
}

Ping start, success and failure from the route

Check the secret the way Vercel's documentation shows, then wrap the work. A ping that cannot be delivered is caught, so it never fails the job:

// app/api/cron/nightly/route.ts
export const dynamic = 'force-dynamic'

const ping = process.env.LASTPING_URL!
const send = (suffix = '', body?: string) =>
  fetch(ping + suffix, { method: body ? 'POST' : 'GET', body }).catch(() => {})

export async function GET(request: Request) {
  const secret = process.env.CRON_SECRET
  if (!secret || request.headers.get('authorization') !== `Bearer ${secret}`) {
    return new Response('Unauthorized', { status: 401 })
  }
  await send('/start')
  try {
    await runNightlyJob()
    await send()
    return Response.json({ ok: true })
  } catch (err) {
    await send('/fail', String(err).slice(0, 10000))
    return new Response('Job failed', { status: 500 })
  }
}

/start arms overrun detection, so a function stopped at its maximum duration shows as a run that started and never finished.

What this catches

The route reports what it can see; the schedule reports the silence.

  • Never invoked: a preview-only cron, disabled crons, an undelivered run, a firewall rule. No ping in the window opens an incident.
  • Invoked but your code never ran: a redirect, a cached response or a 401 from a bad secret sends no ping either, so the same silence raises the alert.
  • Failed: the catch block posts the error to /fail, and the alert goes out at once with the message on the incident.
  • Timed out: the start ping arrived and the finish did not, so overrun detection opens an incident.
  • Recovery: the next successful run closes the incident and sends a recovery notice.

A Vercel app built with an AI assistant can get the same monitor from that assistant: connect it to the LastPing MCP server and ask it to monitor the cron route. More patterns for Node code are in the Node.js guide.

Questions people ask

Front-loaded answers: the most important fact first.

  • Why is my Vercel cron job not running?

    Check that it is on a Production deployment, that the project's cron jobs are not disabled, and that the schedule fits your plan: Hobby allows once a day and runs it any time within the hour. The time is always UTC. If the run happens but your code does not, look for a redirect, a cached response or a CRON_SECRET check that fails.

  • Do Vercel cron jobs run on preview deployments?

    No. Vercel triggers cron jobs on the production deployment URL, and its troubleshooting guide says cron jobs only run on Production deployments.

  • What are the Vercel cron job limits?

    Every plan allows 100 cron jobs per project. Hobby runs a cron at most once per day, with per-hour precision, and a more frequent expression fails the deployment. Pro and Enterprise run as often as once per minute, within the minute specified. A cron job also has the duration limits of a Vercel Function.

  • Does Vercel retry a failed cron job?

    No. Vercel does not retry an invocation if a cron job fails, and delivery is best effort, so a run can also be missed without a log. Make the job idempotent, and use an outside check to learn about the missed and failed runs.

  • What timezone do Vercel cron jobs use?

    Always UTC. Vercel cron expressions also accept no names such as MON or JAN, and day of month and day of week cannot both be set. The cron expression explainer converts a UTC schedule to your own zone.

  • How do I get alerted when a Vercel cron job does not run?

    Create a LastPing monitor with the cron's expression in UTC and a grace window, then have the route ping /start, the monitor URL on success and /fail on error. A run that never arrives sends no ping and opens an incident; on Hobby, make the grace window longer than an hour.

  • Is this monitoring free?

    Yes. LastPing is free for individuals with no monitor cap. See how it compares to Healthchecks.io and Cronitor.

FREE FOR INDIVIDUALS · FULLY HOSTED

The cron run that never arrived left no log. This will notice.

Three pings in the route now beat finding a gap in your data later. First alert wired up in minutes, and LastPing monitors itself the same way.