Vercel cron on the Hobby plan: limits and an alternative

Vercel Cron Jobs call a route of your project on a schedule written in vercel.json. On the Hobby plan they come with three limits that are easy to meet and hard to work around: once a day, sometime within the hour, in UTC. This page gives the limits as Vercel documents them today, says when they do not matter, and shows how to call the same route from an outside scheduler when they do. The route stays the same; only the caller changes.

The limits, from Vercel's documentation

From Usage & Pricing for Cron Jobs and Cron Jobs, checked on 15 October 2026:

HobbyProEnterprise
Cron jobs per project100100100
Minimum intervalonce a dayonce a minuteonce a minute
Scheduling precisionwithin the hour (±59 min)per minuteper minute
Time zoneUTCUTCUTC

What that means on Hobby:

  • Once a day. An expression that would run more often fails the deployment: "Hobby accounts are limited to daily cron jobs. This cron expression would run more than once per day."
  • Within the hour. "A cron job configured as 0 1 * * * (every day at 1 am) will trigger anywhere between 1:00 am and 1:59 am."
  • UTC, always, on every plan. "The timezone is always UTC."
  • No retries, on every plan. "Vercel will not retry an invocation if a cron job fails."

Two more things worth knowing. The number of jobs is no longer the limit it was: since 20 January 2026 every plan allows 100 per project, and articles that say "two cron jobs on Hobby" describe the old per-team cap. And the Hobby plan itself is for "non-commercial, personal use only", by Vercel's Hobby plan page; a business project belongs on Pro whatever its cron needs.

When Hobby cron is fine

A daily cleanup, a cache refresh, a sitemap rebuild: anything that has to happen once a day and does not care whether it is 03:00 or 03:40 UTC. For those, keep the vercel.json entry. Nothing below is an improvement.

When it is not

A digest for people in Paris at 09:00 is the opposite case. Paris is UTC+2 in summer and UTC+1 in winter, and Hobby adds up to 59 minutes on top:

vercel.json scheduleSummer (until 24 October 2026)Winter (from 25 October 2026)
0 7 * * *09:00–09:59 in Paris08:00–08:59 in Paris
0 8 * * *10:00–10:59 in Paris09:00–09:59 in Paris

There is no expression that lands at 09:00 all year, and on Hobby there is no way to run hourly and pick the right hour in code, because hourly is refused at deploy time. On Pro you could run every hour and check the local time in the route; Vercel still reads the expression in UTC.

The alternative: call the route at a set local time

Vercel documents a way to protect a cron route: a CRON_SECRET environment variable, which Vercel sends as Authorization: Bearer <secret> when it calls the route, and which the route checks. Any other scheduler can send the same header, so the same route serves both.

1. The route

A Next.js App Router route handler, with Vercel's recommended check:

import { sendDailyDigest } from '@/lib/digest';

export async function GET(request: Request) {
  const cronSecret = process.env.CRON_SECRET;
  if (!cronSecret || request.headers.get('authorization') !== `Bearer ${cronSecret}`) {
    return new Response('Unauthorized', { status: 401 });
  }

  // 1 on a normal run. Clockwork sends 2 or 3 when it retries a failed run;
  // Vercel's own cron does not send the header at all.
  const attempt = Number(request.headers.get('x-clockwork-attempt') ?? '1');

  const sent = await sendDailyDigest({ isRetry: attempt > 1 });
  return Response.json({ ok: true, sent });
}

sendDailyDigest is your own code. If the project already has a CRON_SECRET for Vercel's cron, keep it; otherwise add one in Settings → Environment Variables (Vercel recommends a random string of at least 16 characters) and redeploy. Then delete the route's entry from the crons array in vercel.json, or both schedulers will call it.

2. The job

With Clockwork, the call is a cron job in your time zone. You need an API key with the clockwork scope; see API Keys.

curl -X POST https://api.vernesoft.com/v1/clockwork/jobs \
  -H "Authorization: Bearer $VERNE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Daily digest",
    "schedule": "0 9 * * *",
    "timezone": "Europe/Paris",
    "url": "https://yourapp.com/api/cron/daily-digest",
    "method": "GET",
    "headers": { "Authorization": "Bearer '"$CRON_SECRET"'" }
  }'

Every day at 09:00 Paris time — 07:00 UTC in summer, 08:00 UTC in winter — Clockwork sends GET /api/cron/daily-digest with your Authorization header and its own X-Clockwork-Attempt. The answer, {"ok":true,...} or whatever went wrong, is in the job's run history. The rules for the two nights a year when the clocks change are under Timezones and daylight saving.

3. Three things to get right

  • Use your production domain. Under Vercel's Standard Protection, the setting it recommends, every URL of the project except production domains asks for a Vercel login, which an outside scheduler does not have. A preview URL, or a deployment's own *.vercel.app address, answers with a login page instead of running the route.
  • Answer within 30 seconds. Clockwork waits 30 seconds for an answer; your Hobby function may run for up to 300. A request that is still running at 30 seconds counts as failed and is retried — while the first one may still be sending emails. Keep the route short, or have it start the work and answer at once.
  • Make a retry harmless. A 5xx, a 429 or no answer is retried after one minute and after five minutes; a 4xx is not. The x-clockwork-attempt header tells the route when a request is a retry, so it can check whether today's digest already went out. See Retries.

The secret is stored in the job's headers, and anyone who can read your Clockwork jobs — your dashboard, or an API key with the clockwork scope — can read it. Use a CRON_SECRET that opens your cron routes and nothing else.

4. Try it by hand

Before the first scheduled run, call the route the way Clockwork will:

curl -i https://yourapp.com/api/cron/daily-digest \
  -H "Authorization: Bearer $CRON_SECRET"

200 and {"ok":true,...} means the job will work; 401 means the secret in the request is not the one the deployment has.

Where to go next


Scheduled jobs are part of Nautilus by Verne Software, sign-in and user management for SaaS teams; the same scheduler runs its automatic deletion of inactive accounts. One plan, priced by active users, hosted in France. Create a free account or see the plans.