Skip to content
Guide · 6 min read

Vercel cron — Hobby vs Pro, and the things you need to know either way

Cron on Vercel is an HTTP request, not a job. Hobby caps you at one a day. Pro lifts that. Here is what changes between the two and what stays awkward on both.

Verified by maintainer·Last updated

Vercel cron is structurally different from Linux cron, Kubernetes CronJob, or AWS EventBridge. It is not a process scheduler. It is an HTTP request scheduler. When your cron fires, Vercel makes a GET request to a route on your deployed app. Whatever happens inside that route handler — querying a DB, sending an email, kicking off a queue — runs inside your normal serverless function, with the same cold-start, timeout, and memory constraints as a user request.

That model has consequences. Some are nice (no separate scheduling infrastructure to maintain). Some are sharp (a slow scheduled job that breaches your function timeout looks identical to an aborted user request).

The vercel.json shape

{
  "crons": [
    { "path": "/api/cron/rollup", "schedule": "0 9 * * 1-5" },
    { "path": "/api/cron/cleanup", "schedule": "0 2 * * *" }
  ]
}

Each entry maps a schedule to a deployed route. The path is relative; cron fires hit your production deployment URL (not a preview URL — Vercel only schedules production).

Hobby plan: one fire per day, total

The most important Hobby limit: cron jobs are restricted to at most one fire per day.0 9 * * 1-5 (every weekday at 9 AM = five fires per week) is rejected at deploy time on Hobby. 0 9 * * 1 (only Monday) passes. 0 9 1 * * (first of the month) passes. The rule is enforced server-side; you cannot work around it by claiming the cron does less than it does.

What "at most once per day" means in practice: across any rolling 24-hour window, the cron fires zero or one times. 0 9 * * * (daily at 9 AM) is fine because that is exactly one fire per day. 0,30 9 * * * is rejected — that would be two.

If your scheduling need is "every weekday at 9 AM" and you are on Hobby, your options are: upgrade to Pro, or split the work to fire at a daily cadence and self-check for "is today a weekday" inside the handler.

Pro plan: still limited, but liveable

Pro removes the once-per-day cap. The remaining limits, as of Q2 2026:

  • Up to 40 cron jobs per project.
  • Minimum interval depends on plan; Pro currently allows every-minute fires.
  • Each cron invocation is a serverless function invocation, billed per-execution like any other request.

For a side project that is outgrowing Hobby cron, Pro at $20/month is the cleanest path. For a serious cron workload, hosting on Railway, Render, or Cloudflare Workers is cheaper.

UTC only, no timezone option

Like GitHub Actions, Vercel schedules are interpreted in UTC. No timezone field, no override. If you want "9 AM in users' local time" you need to either schedule in UTC and do the math, or compute the timezone inside the handler and early-return if the current local time is not the target hour.

No alphabetic aliases

Vercel's parser does not accept MON, JAN, etc. Use numeric forms. The good news: with no aliases, the day-of-week numbering follows Unix convention (1 = Monday, 0 and 7 = Sunday).

Authentication — the CRON_SECRET pattern

Cron-triggered endpoints are public HTTP routes. Anyone who knows the path can call them. Vercel does not auto-secure them. The standard pattern: check a shared secret in the handler.

// app/api/cron/rollup/route.ts
export async function GET(req: Request) {
  const auth = req.headers.get('authorization');
  if (auth !== `Bearer ${process.env.CRON_SECRET}`) {
    return new Response('Unauthorized', { status: 401 });
  }
  // ... real work
}

Vercel automatically adds an Authorization: Bearer <your CRON_SECRET> header to scheduled invocations when you set the CRON_SECRET environment variable on the project. Without this check, anyone who finds the route path can fire your cron whenever they want.

Local testing — vercel dev does not fire crons

Cron schedules are only honoured in production. Locally, vercel dev ignores the crons array. To test a cron handler before deploying, hit the route directly with curl or Postman.

The serverless-function trap

Vercel cron handlers run inside the same serverless function runtime as user-facing API routes. That means:

  • Timeout caps apply. 10 seconds on Hobby. 60 seconds on Pro for Node.js functions, longer on Edge or with Fluid Compute. A long-running cron job will be killed mid-execution.
  • No long-lived connections. If you need to drain a queue, do it in batches, returning between batches.
  • Stateless. No filesystem you can rely on. Anything "remembered" must be in a database.

For anything heavier than "send a webhook", the right pattern is to have the cron handler enqueue work to a queue (QStash, SQS, Inngest) and let a separate worker do the actual job. The cron is just the trigger.

When to leave Vercel cron for something else

Three signals that you have outgrown it:

  1. Your cron handler regularly approaches the function timeout.
  2. You need multiple coordinated schedules with explicit ordering (run B only after A finishes).
  3. You need timezone-aware schedules and the "run daily and check inside" workaround is getting hairy.

At that point, the natural next steps are Cloudflare Workers + cron triggers (cheap, fast, with a 30-second timeout but unlimited per-day fires), Railway with a long-running worker container, or AWS EventBridge Scheduler triggering a Lambda or ECS task.

Read next

all guides →
Spotted something wrong? Use the contact form.