Fehlerbehebung · Vercel
Vercel-Cronjob läuft nicht? Diese Punkte zuerst prüfen
Warum ein Vercel-Cronjob nicht läuft oder auslöst — nur Production-Deploys, Hobby-Plan-Limits mit Stundenfenster-Timing, CRON_SECRET-401s, maxDuration-Timeouts — und wie Sie es beheben.
Vercel-Crons sind bequem — bis sie es nicht mehr sind. Die meisten „läuft nie“-Fälle gehen auf eine von fünf Ursachen zurück — der Reihe nach.
Diagnose in 30 Sekunden:
| Symptom | Wahrscheinlichste Ursache | Abschnitt |
|---|---|---|
| Läuft nie, Dashboard zeigt keinen Cron | Config nicht im Production-Deploy | §1 |
| Läuft zu scheinbar zufälliger Zeit | Hobby-Plan-Stundenfenster | §2 |
| Läuft, liefert aber 401/403 | Auth-Middleware / CRON_SECRET | §3 |
| Startet, wird aber nie fertig | maxDuration-Timeout | §4 |
| Läuft zur „falschen“ Stunde | Zeitpläne sind nur UTC | §5 |
1. Crons laufen nur auf dem Production-Deployment
In vercel.json definierte Cronjobs werden nur für das aktuelle
Production-Deployment aktiviert. Sie laufen nicht auf Preview-Deployments, und
Änderungen an der crons-Konfiguration greifen erst, wenn Sie erneut nach
Production deployen.
{
"crons": [
{ "path": "/api/reports/nightly", "schedule": "0 3 * * *" }
]
}
Prüfen Sie das Dashboard: Project → Settings → Cron Jobs sollte den Job mit seiner nächsten geplanten Laufzeit anzeigen. Fehlt er dort, hat die Konfiguration nie ein Production-Deployment erreicht. Häufige Wege dorthin:
- die
vercel.json-Änderung wurde gemerged, aber der Production-Deploy schlug fehl oder wurde übersprungen; - die Datei liegt in einem Unterverzeichnis, das nicht das von Vercel gebaute Projekt-Root ist;
- ein Tippfehler im
crons-Key — unbekannte Konfiguration ignoriert Vercel stillschweigend.
Außerdem: Crons sind pausiert, solange das Projekt pausiert ist. Maßgeblich ist immer das aktuelle Production-Deployment.
2. Im Hobby-Plan ist das Timing ungefähr — absichtlich
Hobby-Plan-Crons sind auf 2 Cronjobs limitiert, die höchstens einmal
täglich laufen — und der Aufruf kann irgendwann innerhalb der Stunde der
geplanten Zeit erfolgen. 0 3 * * * heißt „irgendwann zwischen 03:00 und 03:59
UTC“ — nicht 03:00. Das ist dokumentiertes Verhalten, kein Bug, und überrascht
regelmäßig alle, die einen „Mitternachts-Cron“ um 00:47 feuern sehen.
Zwei Folgeeffekte:
- Ein Zeitplan häufiger als täglich (etwa
*/10 * * * *) ist im Hobby-Plan ungültig — das Deploy warnt, und der Cron läuft nicht wie geschrieben. - Weil das Fenster eine Stunde breit ist, können zwei aufeinanderfolgende Läufe ~23 bis ~25 Stunden auseinanderliegen. Alles, was „exakt 24h seit dem letzten Lauf“ annimmt, braucht Spielraum.
Für minutengenaues Timing oder untertägige Zeitpläne braucht es den Pro-Plan — oder einen externen Scheduler, der dieselbe Route zeitgenau aufruft (siehe letzter Abschnitt). Vertiefung: Vercel-Cron-Limits erklärt.
3. Die Route lehnt den Request ab (401/403)
Vercel ruft Ihren Cron als einfachen GET-Request auf. Sitzt die Route hinter Ihrer eigenen Auth-Middleware, wird der Cron wie jeder anonyme Aufrufer abgewiesen.
Das unterstützte Muster: Umgebungsvariable CRON_SECRET setzen. Vercel sendet
sie als Bearer-Token mit, und Sie prüfen sie im Handler:
export function GET(req: Request) {
const auth = req.headers.get("authorization");
if (auth !== `Bearer ${process.env.CRON_SECRET}`) {
return new Response("Unauthorized", { status: 401 });
}
// ... der eigentliche Job
}
Drei Stolperfallen dabei:
- Nach dem Setzen oder Rotieren von
CRON_SECRETneu deployen — Env-Änderungen wirken nicht auf das laufende Production-Deployment. - Globale Middleware (
middleware.ts) läuft vor dem Handler — leitet sie unauthentifizierte Requests zur Login-Seite um, bekommt der Cron einen 307 und der Handler läuft nie. Cron-Pfad ausnehmen oder den Header in der Middleware prüfen. - Deployment Protection (passwort-/SSO-geschützte Deployments) blockiert Cron-Aufrufe genauso wie anonyme Besucher.
4. Die Function läuft mitten im Job in den Timeout
Ein Cron-Aufruf ist ein normaler Serverless-Function-Call mit denselben
maxDuration-Limits wie jeder andere. Ein Nightly-Job, der über das Limit
gewachsen ist, wird mitten im Lauf abgebrochen — der Zeitplan feuert, die Arbeit
wird nicht fertig.
Prüfen Sie Logs / Observability auf Task timed out-Einträge und erhöhen Sie
maxDuration in der Route-Konfiguration (Limits je nach Plan):
export const maxDuration = 300; // Sekunden — Route-Segment-Konfiguration
Braucht der Job legitim länger als das Plan-Maximum: in Batches aufteilen, oder die schwere Arbeit in eine Queue verlagern und den Cron nur enqueuen lassen.
5. Der Zeitplan ist UTC und nur 5 Felder
Keine Zeitzonen-Unterstützung, kein Sekundenfeld, keine @daily-Abkürzungen.
0 9 * * * ist ganzjährig 09:00 UTC — Ihr Berliner 9-Uhr-Job driftet mit der
Sommerzeit um eine Stunde, und es gibt in Vercel keine Einstellung dagegen.
Unsicher, was ein Ausdruck tut? Der Cron-Ausdruck-Erklärer zeigt konkrete nächste Laufzeiten.
Das eigentliche Problem: keine Retries, keine Alerts
Vercel-Cron gibt Ihnen einen Trigger und ein Log — sonst nichts. Ein fehlgeschlagener Lauf wird nicht wiederholt. Niemand bekommt eine E-Mail. Ein Job, der drei Wochen lang jede Nacht mit 500 fehlschlägt, sieht exakt aus wie ein gesunder, solange niemand die Logs liest.
Zwei robuste Upgrades, mit zunehmender Kontrolle:
-
Vercel-Cron behalten, Heartbeat ergänzen. Die letzte Zeile des Handlers pingt einen Monitor; ein ausgebliebener oder fehlgeschlagener Lauf alarmiert innerhalb Ihrer Grace-Zeit:
await fetch("https://ping.steadycron.com/<ihr-ping-token>");Weil der Ping nur feuert, wenn der Handler fertig wird, fängt das alles oben Genannte auf einmal: fehlende Production-Deploys, 401s, Timeouts und schlichte Crashes. Einrichtung unter Heartbeat-Monitoring.
-
Den Trigger aus Vercel herausziehen. Route behalten,
crons-Konfiguration löschen und einen externen Scheduler aufrufen lassen — mit exaktem Timing, Zeitzonen pro Job, Retries mit Backoff und einem Ausführungsprotokoll jeder Response. Genau das machen SteadyCrons HTTP-Jobs; die Route bleibt durch denselbenCRON_SECRET-Header geschützt, undsteadycron import vercelkonvertiert Ihre bestehendevercel.jsonin einem Befehl. Siehe Migration von Vercel-Cron.
Häufige Fragen
Warum läuft mein Vercel-Cronjob überhaupt nicht?
Meist hat die crons-Konfiguration nie ein Production-Deployment erreicht — Cronjobs werden nur auf dem aktuellen Production-Deploy aktiviert, nie auf Previews. Prüfen Sie Project → Settings → Cron Jobs: Fehlt der Job dort mit nächster Laufzeit, deployen Sie erneut nach Production.
Wie viele Cronjobs erlaubt der Vercel-Hobby-Plan?
Der Hobby-Plan erlaubt 2 Cronjobs, jeder höchstens einmal täglich, und der Aufruf landet irgendwann innerhalb der geplanten Stunde. Minutengenaues Timing und häufigere Zeitpläne erfordern den Pro-Plan — oder einen externen Scheduler, der dieselbe Route aufruft.
Warum läuft mein Vercel-Cron zu einer zufälligen Zeit innerhalb der Stunde?
Das ist dokumentiertes Hobby-Plan-Verhalten: 0 3 * * * bedeutet „irgendwann zwischen 03:00 und 03:59 UTC“, nicht Punkt 03:00. Es ist eine bewusste Lastverteilung, kein Bug.
Warum liefert mein Vercel-Cron 401 Unauthorized?
Vercel ruft Crons als einfache GET-Requests auf. Prüft Ihre Route Authentifizierung, setzen Sie die Umgebungsvariable CRON_SECRET — Vercel sendet sie als Bearer-Token — und verifizieren Sie sie im Handler. Nach dem Setzen oder Rotieren: neu deployen.
Wiederholt Vercel einen fehlgeschlagenen Cronjob oder alarmiert mich?
Nein. Ein fehlgeschlagener Lauf wird nicht wiederholt, und es gibt keine Benachrichtigung — ein Job, der jede Nacht mit 500 fehlschlägt, sieht identisch zu einem gesunden aus, solange niemand die Logs liest. Ergänzen Sie einen Heartbeat-Ping oder verlagern Sie den Trigger zu einem Scheduler mit Retries und Alerting.