Skip to content

robfig/cron

Developer UtilitiesScheduler/Job ManagementGo

What it is

A cron-expression job scheduler for Go, supporting standard cron syntax, descriptive shortcuts, time zones and second-level precision.

Register functions against cron expressions and start the scheduler. Jobs run in their own goroutines; wrappers add recovery, skip-if-still-running and logging.

Installation

go get github.com/robfig/cron/v3

Getting started

The smallest useful thing you can do with it, and what each part means.

Scheduling jobs
c := cron.New(
    cron.WithLocation(time.UTC),      // never schedule in local time on a server
    cron.WithChain(
        cron.Recover(cron.DefaultLogger),      // a panic must not kill the scheduler
        cron.SkipIfStillRunning(cron.DefaultLogger),
    ),
)

c.AddFunc("0 3 * * *", func() { nightlyReport(ctx) })   // 03:00 daily
c.AddFunc("@every 5m", func() { refreshCache(ctx) })

c.Start()
defer c.Stop()
Recover and SkipIfStillRunning are the two wrappers you almost always want: without them a panic stops the scheduler, and a slow job silently overlaps with itself.

Advanced usage

Where the library earns its place over a simpler alternative.

Graceful shutdown
c.Start()

<-ctx.Done()

// Stop() prevents new runs and returns a context that closes when the
// jobs already running have finished.
stopCtx := c.Stop()
select {
case <-stopCtx.Done():
    log.Println("all jobs finished")
case <-time.After(30 * time.Second):
    log.Println("timed out waiting for jobs")
}
Stop returns immediately, so without waiting on its context you may kill the process mid-job. This matters for anything writing to a database.

Errors and fixes

The failures you are most likely to hit, and what actually resolves them.

Jobs run at the wrong time
The scheduler defaults to local time. Set WithLocation(time.UTC) and confirm the expression uses the standard five-field format.
Every replica runs the job
This scheduler is in-process and has no coordination. Guard the job with a database or Redis lock.

Best practices

  • Always use WithLocation — relying on the server's local time zone causes daylight-saving surprises.
  • Add Recover so one panicking job does not stop the whole scheduler.
  • Use SkipIfStillRunning or DelayIfStillRunning for jobs that might overrun their interval.
  • For multi-instance deployments, add a distributed lock — every replica runs its own scheduler.

Background

Why it exists, and what it was reacting to.

robfig/cron is the long-standing default for in-process scheduling in Go — the answer when you want periodic work without deploying a separate scheduler.