Zurück zum Blog
Individuelle Software

Was ist ein Cronjob? Geplante Aufgaben zuverlässig betreiben

Was ist ein Cronjob und wie liest man einen Cron-Ausdruck? Warum geplante Aufgaben wie Nachtberichte, Rechnungsläufe und Backups still ausfallen: doppelte Ausführung auf mehreren Servern, Zeitzonen, verpasste Läufe und Überwachung. Idempotenz mit Periodenschlüssel und das Verhältnis zu Queues.

Cron JobZamanlanmış GörevBackendÖzel Yazılım

Ein Cronjob ist eine Aufgabe, die zu einer festen Zeit oder in regelmäßigen Abständen von selbst läuft: jede Nacht um 02:00 ein Backup, jeden Morgen der Umsatzbericht per E-Mail, stündlich die Sendungsstatus aktualisieren, am Monatsersten die Abo-Rechnungen erstellen. Der Name stammt vom Unix-Programm cron, das den Zeitplan mit einem fünfteiligen Ausdruck der Form „Minute Stunde Tag Monat Wochentag“ beschreibt. Die Aufgabe zu planen ist leicht; schwer ist es sicherzustellen, dass sie nicht doppelt läuft, dass ein verpasster Lauf auffällt und dass sie nicht still kaputtgeht. Dieser Artikel erklärt den Cron-Ausdruck, die häufigsten Fehlerursachen und das sichere Setup, das wir in unseren Projekten für Individualsoftware verwenden.

Wie liest man einen Cron-Ausdruck?

Von links nach rechts stehen die fünf Felder für Minute (0-59), Stunde (0-23), Tag des Monats (1-31), Monat (1-12) und Wochentag (0-6, Sonntag ist 0). Ein Stern bedeutet „jede“, ein Komma eine Liste, ein Bindestrich einen Bereich und ein Schrägstrich eine Schrittweite. Einige Beispiele:

  • 0 2 * * *: täglich um 02:00 (nächtliches Backup, Tagesbericht).
  • */15 * * * *: alle 15 Minuten (Abgleich von Versand- oder Zahlungsstatus).
  • 0 9 * * 1-5: werktags um 09:00 (Morgenübersicht).
  • 0 0 1 * *: Mitternacht am Monatsersten (Monatsrechnungen, Abo-Verlängerungen).
  • 30 23 * * 0: sonntags um 23:30 (wöchentliche Bereinigung, Archivierung alter Datensätze).

Ein Cron-Ausdruck wird in einer Zeitzone ausgewertet, und das ist meist die Serveruhr. Läuft der Server in UTC, bedeutet „0 2 * * *“ in Deutschland je nach Jahreszeit 03:00 oder 04:00. Die Zeitzone des Schedulers wird deshalb ausdrücklich gesetzt und nie dem Standard überlassen; bei der Zeitumstellung können Jobs zwischen 02:00 und 03:00 an einem Tag im Jahr gar nicht oder doppelt laufen.

Wo läuft eine geplante Aufgabe?

  • Server-Crontab: das klassische Setup mit einem Server. Einfach, aber beim Serverwechsel wird der Job vergessen, und er ist nicht mit dem Code versioniert.
  • Scheduler in der Anwendung: Bibliotheken wie node-cron, Quartz oder Hangfire. Lebt mit dem Code, aber laufen mehrere Instanzen, feuert der Job in jeder davon.
  • Plattform-Scheduler: Kubernetes CronJob, geplante Trigger der Cloud-Anbieter, Vercel Cron. Die Plattform löst einmal aus, die Arbeit bleibt in Ihrem Code.
  • Wiederkehrende Jobs in einer Queue: Queues wie BullMQ oder Sidekiq verwalten die Jobdefinition selbst und bringen Wiederholungen und Laufhistorie mit.

Der häufigste Fehler: Der Job läuft doppelt

Skaliert die Anwendung auf zwei Server, skaliert auch der eingebaute Scheduler auf zwei, und der Nachtbericht geht zweimal raus. Bei einem Bericht ist das lästig, bei Rechnungen oder Zahlungsaufträgen ein ernster Fehler. Es reicht nicht, dass der Job einmal ausgelöst wird; die Arbeit selbst muss bei doppelter Ausführung dasselbe Ergebnis liefern. Am robustesten gelingt das mit einem Periodenschlüssel pro Lauf, etwa „Monatsrechnungen, 2026-10“. Beim Start versucht der Job, einen Datensatz mit diesem Schlüssel anzulegen; existiert er schon, hat eine andere Instanz die Arbeit übernommen, und diese Instanz beendet sich. Dieselbe Tabelle hält fest, wann der Job begann, wann er endete und ob er fehlschlug. Es ist dieselbe Idee wie die Ereignis-ID bei Webhook-Empfängern, die wir im Artikel zu Webhooks beschrieben haben.

// Am 1. jedes Monats um 03:00 Uhr türkischer Zeit: '0 3 1 * *' (tz: Europe/Istanbul)
export async function monthlyInvoicesJob(now = new Date()) {
  const period = formatInTimeZone(now, 'Europe/Istanbul', 'yyyy-MM');
  const runKey = 'monthly-invoices:' + period;

  // Eindeutiger Index auf job_runs.run_key: Eine zweite Instanz kommt nicht hinein.
  const run = await db.jobRuns.insertIfAbsent({
    runKey,
    startedAt: new Date(),
    status: 'running',
  });
  if (!run) return; // diese Periode ist erledigt oder läuft gerade

  try {
    const subs = await db.subscriptions.dueForBilling(period);
    for (const sub of subs) {
      // Rechnungen sind je Periode + Abo eindeutig: Ein abgebrochener Lauf startet sicher neu.
      await billing.createInvoiceOnce(sub.id, period);
    }
    await db.jobRuns.finish(runKey, 'succeeded');
  } catch (err) {
    await db.jobRuns.finish(runKey, 'failed');
    throw err; // an das Error-Tracking weitergeben
  }
}
Der gefährlichste Zustand einer geplanten Aufgabe ist nicht der Fehler, sondern der ausgebliebene Lauf: Ein Fehler wird protokolliert und gesehen, ein Job, der nie lief, bleibt still, bis es jemand merkt. Jeder kritische Job braucht eine Überwachung, die beantwortet, wann der letzte erfolgreiche Lauf war.

Verpasste Läufe und Überwachung

Startet der Server genau zur geplanten Zeit neu oder ist der Scheduler während eines Deployments aus, wird dieser Lauf verpasst. Klassisches cron holt verpasste Läufe nicht nach. Kritische Jobs werden deshalb doppelt abgesichert. Erstens verarbeitet der Job nicht nur „jetzt“, sondern jede noch offene Periode: Der Monatsrechnungsjob erledigt jede Periode ohne Rechnungen, nicht nur den aktuellen Monat. Zweitens wird er von außen überwacht: Jeder erfolgreiche Lauf sendet ein Signal an einen Monitoring-Endpunkt, und bleibt es im erwarteten Zeitfenster aus, wird das Team alarmiert. Wohin Fehler und Signale gehen, beschreiben wir im Artikel zu Fehlertracking und Logging; für Backup-Jobs ist diese Überwachung Pflicht, Details unter Datensicherung und Notfallwiederherstellung.

Lang laufende Jobs und Queues

Cron ist ein Auslöser, kein Arbeiter. Zehntausend Abonnenten in einer einzigen Schleife abzurechnen lässt bei einem Fehler in der Mitte offen, welche Rechnungen erstellt wurden, und kann mit dem nächsten Auslösen kollidieren. Richtig ist, dass cron die Arbeit nur in eine Queue legt und Worker jeden Eintrag einzeln mit Wiederholungen verarbeiten. So stoppt ein Fehler bei einem Abonnenten nicht die anderen, und die Last verteilt sich über den Tag. Dieselbe Regel gilt für Jobs, die von einem externen Dienst wie der E-Rechnung abhängen: Wird die Gegenseite langsam, wartet die Queue, statt dass der Job in ein Timeout läuft; siehe E-Rechnungs-Anbindung. Ob Jobs in einem eigenen Dienst oder in derselben Anwendung liegen, ist eine Architekturfrage; siehe Microservices oder Monolith.

Checkliste vor dem Start

  • Ist die Zeitzone ausdrücklich gesetzt, und werden Uhrzeiten der Zeitumstellung vermieden?
  • Wird der Job auch bei mehreren Instanzen der Anwendung nur einmal ausgelöst?
  • Liefert die Arbeit bei doppelter Ausführung dasselbe Ergebnis (Periodenschlüssel, eindeutiger Index)?
  • Wird eine verpasste Periode beim nächsten Lauf nachgeholt?
  • Wird der letzte erfolgreiche Lauf überwacht und bei Verspätung ein Alarm ausgelöst?
  • Ist die Jobdefinition mit dem Code versioniert und in der CI/CD-Pipeline getestet?

Fazit

Ein Cronjob ist ein kleiner Baustein, der die Arbeit erledigt, die nicht vergessen werden darf; klein zu sein rechtfertigt aber nicht, ihn unkontrolliert zu lassen. Eine sichere geplante Aufgabe hat eine ausdrückliche Zeitzone, läuft auf einer einzigen Instanz, richtet bei doppelter Ausführung keinen Schaden an, holt verpasste Perioden nach und meldet sich, wenn sie nicht läuft. Wenn Sie Ihre Rechnungs-, Berichts-, Abgleich- und Backup-Jobs nach diesen Prinzipien aufbauen möchten, sehen Sie sich unseren Service für Individualsoftware an oder fordern Sie direkt ein Angebot an.

Let's Build Your Project

Get a free consultation for your website, mobile app, or corporate software project.

Get a Free QuoteExplore our Custom Software service