Why a Vercel cron job fails silently and how to detect it
A Vercel cron job can silently fail because delivery is best-effort: Vercel will not retry a failed invocation, so a transient network error or a function that throws will not be attempted again until the next scheduled slot, and Vercel does not send an alert that the run failed. To detect silent failures, have the cron route ping an external monitor on success and alert when the ping doesn't arrive.
Why doesn't Vercel tell me when a cron job fails?
- Best-effort delivery: Vercel's cron invocations are best-effort. A transient error stopping the HTTP request from reaching your function is not retried — the run is simply lost.
- No retry on failure: unlike some schedulers, Vercel does not retry a failed invocation. The next attempt is the next scheduled slot.
- No failure notification by default: Vercel does not send an email or webhook when a cron invocation fails or is dropped.
- Logs as the only record: the Vercel dashboard logs each invocation attempt, but you must actively check them — there is no push alert.
How do I detect a silently failed Vercel cron job?
Ping an external monitor at the end of the cron route, after the work succeeds. If the function throws or the invocation is dropped, no ping arrives and the monitor alerts:
export async function GET(request: Request) {
// Verify the request is from Vercel Cron (prevents unauthorized triggers).
const authHeader = request.headers.get("authorization");
if (authHeader !== `Bearer ${process.env.CRON_SECRET}`) {
return new Response("Unauthorized", { status: 401 });
}
try {
await runNightlyJob();
// Report success. If runNightlyJob() throws, this is skipped.
await fetch("https://ping.cronshield.com/<your-check-id>", { signal: AbortSignal.timeout(10000) });
return new Response("ok");
} catch (err) {
console.error("cron failed:", err);
return new Response("failed", { status: 500 });
}
}How do I see if a cron invocation was dropped vs. failed?
In the Vercel dashboard, open your project, go to Deployments, and check the Function logs for the cron route. A dropped invocation (network error before the function was reached) may show no log entry at all. A function that threw will show an error log. A missed ping tells you something went wrong; the logs tell you which category it falls into.
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
- Does Vercel retry a cron invocation that returned a 500?
- No. Vercel does not retry cron invocations on failure. If your function returns a 500 or throws, the invocation is recorded as failed and the next attempt is the next scheduled slot.
- What is CRON_SECRET and do I need it?
- CRON_SECRET is an environment variable Vercel automatically provides. Vercel sets an Authorization: Bearer <CRON_SECRET> header on each cron invocation. Verifying this header in your route prevents unauthorized callers from triggering the job. Add the check in production.