Monitor Vercel Cron Jobs
Vercel doesn't retry a failed cron invocation, and delivery is best effort, so a run can be missed without anything in your logs. Ping a URL when the work completes and we email you when a run is late or fails.
You need: a Lateping account, a Vercel project with a cron job, about 5 minutes.
What can go wrong
A Vercel Cron Job is a scheduled GET request to one of your routes. When that request fails or never arrives, the only trace is in the function logs, if there is one at all.
Set it up
Create a check
Create a check in Lateping with the same cron expression as the job and the timezone
UTC(Vercel cron schedules run in UTC). Copy its ping URL intoLATEPING_URLin your project’s environment variables.On the Hobby plan, Vercel may run a daily job at any point within the scheduled hour, so set the grace period to at least an hour plus the job’s run time. On Pro and Enterprise, jobs start within the scheduled minute.
Ping from the route
{ "crons": [{ "path": "/api/cron/nightly", "schedule": "17 2 * * *" }] }export async function GET(request: Request) { // Vercel sends CRON_SECRET as a bearer token. Reject anything else. if (request.headers.get("authorization") !== `Bearer ${process.env.CRON_SECRET}`) { return new Response("Unauthorized", { status: 401 }); } await ping("/start"); try { await runJob(); } catch (err) { await ping("/fail"); throw err; // keep the invocation marked as failed in Vercel's logs } await ping(""); return Response.json({ ok: true }); } async function runJob() { // Your scheduled work goes here. Await everything it starts. } async function ping(path: string) { try { const res = await fetch(process.env.LATEPING_URL + path, { method: "POST", signal: AbortSignal.timeout(10_000), }); if (!res.ok) console.error(`Lateping ping ${path || "/"} returned ${res.status}`); } catch (err) { console.error(`Lateping ping ${path || "/"} failed: ${err}`); } }- Set
CRON_SECRETto a random string of at least 16 characters. Without the check, anyone who finds the route can run your job and send pings. - The success ping is sent only after
runJobfinishes without throwing. If it throws,/failalerts straight away. - If the invocation never arrives, or the function times out before the last line, no success ping comes and the check alerts after the grace period.
ping()never throws, so a network problem reaching Lateping can’t fail a job that worked.
- Set
Test it
Cron jobs only run on production deployments, and there’s no local cron runner. Deploy, then call the route yourself with the secret:
curl -H "Authorization: Bearer $CRON_SECRET" https://your-app.vercel.app/api/cron/nightlyThe check should show a start ping and a success ping.
Notes
- Await the pings. Once the function returns, the platform can stop it before an un-awaited request is sent.
- Keep the job inside the function’s maximum duration. A run that hits the limit is killed before its success ping, which is what you want to hear about.
- If a job can run longer than its interval, Vercel may start a second run while the first is still going. Use a lock, or give the check a longer grace period and fix the overlap.
- One check per schedule. If several schedules share a path, branch on the
x-vercel-cron-scheduleheader and give each its own check URL. - Alerts go by email. On Pro and above they can also go to Slack, Discord or a webhook.