beginner 8 min read

How to Set Up Cron Jobs on Vercel (Cron Jobs Guide)

Learn how to set up Vercel Cron Jobs to run scheduled serverless functions. Covers vercel.json config, API routes, plan limits, and debugging.

Prerequisites

  • Vercel account
  • A deployed project (Next.js, Nuxt, SvelteKit, etc.)
  • vercel.json in project root

What Are Vercel Cron Jobs?

Vercel Cron Jobs let you schedule serverless function invocations using cron expressions. Define your schedules in vercel.json, and Vercel calls your API routes on the specified schedule.

Under the hood, Vercel uses a managed scheduler — you don't need to manage any infrastructure. Your functions run as standard serverless functions with the same timeout and memory limits.

Quick Start

Step 1: Create an API route

For Next.js (app/api/cron/route.ts):

typescript
export async function GET(request: Request) {
  // Verify the request is from Vercel Cron
  const authHeader = request.headers.get('authorization');
  if (authHeader !== `Bearer ${process.env.CRON_SECRET}`) {
    return new Response('Unauthorized', { status: 401 });
  }

  // Your scheduled task logic
  await processQueue();

  return Response.json({ success: true });
}

Step 2: Configure the schedule in vercel.json

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

Step 3: Add a CRON_SECRET environment variable

In your Vercel project settings, add a CRON_SECRET env var. This prevents unauthorized access to your cron endpoint.

Plan Limits

Vercel Cron Jobs have different limits by plan:

  • Hobby (free): 2 cron jobs, daily max frequency (once per day minimum), 10-second function timeout
  • Pro: 40 cron jobs, per-minute frequency, 300-second function timeout
  • Enterprise: 100 cron jobs, per-minute frequency, 900-second function timeout

If you need sub-minute scheduling, use an external service like Inngest, Trigger.dev, or a traditional server with cron.

Common Cron Expressions

Vercel uses standard 5-field cron syntax:

  • 0 * * * * — Every hour
  • 0 0 * * * — Daily at midnight UTC
  • 0 9 * * 1-5 — Weekdays at 9 AM UTC
  • 0 0 1 * * — First day of every month
  • */5 * * * * — Every 5 minutes (Pro+ only)

All schedules run in UTC. Vercel does not support timezone configuration for cron schedules.

Every weekday at 9:00 AM

Next runs (UTC):

Mon, May 18, 2026 09:00

Tue, May 19, 2026 09:00

Wed, May 20, 2026 09:00

Framework Examples

Next.js (App Router):

typescript
// app/api/cleanup/route.ts
export const dynamic = 'force-dynamic';

export async function GET(request: Request) {
  await db.deleteExpiredSessions();
  return Response.json({ cleaned: true });
}

Nuxt 3:

typescript
// server/api/cron.get.ts
export default defineEventHandler(async (event) => {
  const auth = getHeader(event, 'authorization');
  if (auth !== `Bearer ${process.env.CRON_SECRET}`) {
    throw createError({ statusCode: 401 });
  }
  await cleanupExpired();
  return { success: true };
});

vercel.json (same for all frameworks):

json
{
  "crons": [
    { "path": "/api/cleanup", "schedule": "0 */6 * * *" }
  ]
}

Debugging Vercel Cron Jobs

Check cron execution logs:

bash
vercel logs --follow

Or view in the Vercel dashboard under your project's Logs tab. Cron invocations are logged with the path and schedule.

Common issues:

  • Cron not running: Verify vercel.json is in the project root and the path matches an actual API route
  • 401 errors: Check your CRON_SECRET env var is set correctly
  • Timeout errors: Hobby plan has a 10-second limit. Optimize your function or upgrade to Pro
  • Only runs in production: Vercel Cron Jobs only run on production deployments, not preview branches

Test locally:

bash
curl http://localhost:3000/api/cron -H "Authorization: Bearer your-secret"

Production Tips

Always verify the CRON_SECRET. Without authentication, anyone can trigger your cron endpoint.

Idempotent functions: Vercel may occasionally retry failed invocations. Make your functions safe to run multiple times.

Long-running tasks: If your task exceeds the function timeout, break it into smaller chunks or use a queue:

typescript
export async function GET() {
  // Process in batches
  const batch = await db.getUnprocessedItems(100);
  await Promise.all(batch.map(processItem));
  return Response.json({ processed: batch.length });
}

Monitoring: Check Vercel's built-in monitoring or add a health check ping at the end of your function.

Frequently Asked Questions