Skip to content

Monitor Vercel Cron Jobs

Vercel’s cron docs say it “will not retry an invocation if a cron job fails” and that delivery “is best effort”. A network error can stop a scheduled request from reaching your function, and then “no runtime log is created for that scheduled run”. A Chronos monitor alerts on a failed run and on a run that never happened.

Vercel triggers a cron job with an HTTP GET to a route on your production deployment. The route pings Chronos when the work finishes.

  1. Create a monitor with the same cron expression as vercel.json, and set its timezone to UTC, since Vercel cron schedules are always UTC.
  2. Add the monitor’s ping URL to the project’s environment variables as CHRONOS_PING_URL. Add a CRON_SECRET variable too, so only Vercel can call the route.
  3. Ping at the end of the handler, and ping /fail when the work throws. In a Next.js route handler:
app/api/cron/route.ts
const pingUrl = process.env.CHRONOS_PING_URL!;
async function ping(path: string, body?: string) {
try {
const res = await fetch(pingUrl + path, { method: 'POST', body, signal: AbortSignal.timeout(10_000) });
if (!res.ok) console.error('chronos ping failed', res.status, await res.text());
} catch (err) {
console.error('chronos ping failed', err);
}
}
export async function GET(request: Request) {
if (request.headers.get('authorization') !== `Bearer ${process.env.CRON_SECRET}`) {
return new Response('Unauthorized', { status: 401 });
}
try {
await runJob();
} catch (err) {
await ping('/fail', String(err));
return new Response('Job failed', { status: 500 });
}
await ping('');
return new Response('OK');
}
vercel.json
{
"crons": [{ "path": "/api/cron", "schedule": "0 3 * * *" }]
}

Vercel sends CRON_SECRET as Authorization: Bearer <secret> on every cron invocation. Await each ping. A serverless function can stop once it returns its response, and an unawaited ping may never go out. If the function hits its maximum duration, neither ping is sent, and the monitor goes down when the grace period passes.

On the Hobby plan, cron jobs run at most once a day, and Vercel “may invoke these cron jobs at any point within the specified hour”. Set the grace period to at least 1 hour plus the job’s run time. On other plans, Vercel invokes the job within the scheduled minute, so the job’s run time plus a few minutes is enough.

Call the route once with the secret, against your production deployment:

Terminal window
curl -H "Authorization: Bearer $CRON_SECRET" https://your-app.vercel.app/api/cron

The monitor moves from new to up. To test the alert, make runJob throw, deploy, and call the route again. The monitor goes down, and the alert shows the error message.