A Golang job scheduling library that lets you run Go functions at pre-determined intervals using cron expressions, fixed durations, daily, weekly, monthly, or one-time schedules with support for distributed deployments.
Schedule jobs at fixed intervals using DurationJob and DurationRandomJob.
// Every 30 seconds
j, _ := s.NewJob(
gocron.DurationJob(30*time.Second),
gocron.NewTask(func() {
fmt.Println("30 seconds elapsed")
}),
)
// Every 5 minutes
j, _ = s.NewJob(
gocron.DurationJob(5*time.Minute),
gocron.NewTask(func() {
fmt.Println("5 minutes elapsed")
}),
)
// Every hour
j, _ = s.NewJob(
gocron.DurationJob(time.Hour),
gocron.NewTask(func() {
fmt.Println("1 hour elapsed")
}),
)package main
import (
"fmt"
"time"
"github.com/go-co-op/gocron/v2"
)
func main() {
// Create scheduler
s, err := gocron.NewScheduler()
if err != nil {
panic(err)
}
defer s.Shutdown()
// Add job
j, err := s.NewJob(
gocron.DurationJob(5*time.Second),
gocron.NewTask(func() {
fmt.Println("running every 5 seconds")
}),
)
if err != nil {
panic(err)
}
fmt.Printf("Job %s added\n", j.ID())
// Start scheduler
s.Start()
// Block forever
select {}
}Wait N duration AFTER job completes, not from start time:
j, _ := s.NewJob(
gocron.DurationJob(5*time.Minute),
gocron.NewTask(func() {
// Takes variable time to complete
doWork()
}),
gocron.WithIntervalFromCompletion(),
)
// Ensures 5 minutes of rest between executions// Default: interval from start
// If job takes 2 minutes, next run is 3 minutes after completion (5 - 2)
j1, _ := s.NewJob(
gocron.DurationJob(5*time.Minute),
gocron.NewTask(func() {
time.Sleep(2 * time.Minute)
}),
)
// With interval from completion
// If job takes 2 minutes, next run is 5 minutes after completion
j2, _ := s.NewJob(
gocron.DurationJob(5*time.Minute),
gocron.NewTask(func() {
time.Sleep(2 * time.Minute)
}),
gocron.WithIntervalFromCompletion(),
)// Process queue with consistent wait time between batches
j, _ := s.NewJob(
gocron.DurationJob(10*time.Second),
gocron.NewTask(func() {
batch := fetchQueueBatch()
processBatch(batch) // Variable duration
}),
gocron.WithIntervalFromCompletion(),
gocron.WithName("queue-processor"),
)
// Always waits 10 seconds after batch completesPrevent thundering herd by randomizing intervals:
// Run every 4-6 minutes (prevents all instances running simultaneously)
j, _ := s.NewJob(
gocron.DurationRandomJob(4*time.Minute, 6*time.Minute),
gocron.NewTask(func() {
checkHealth()
}),
)// Multiple instances won't all hit the health endpoint at once
j, _ := s.NewJob(
gocron.DurationRandomJob(
50*time.Second,
70*time.Second,
),
gocron.NewTask(func() {
status := checkServiceHealth()
if !status.OK {
sendAlert(status.Error)
}
}),
gocron.WithName("health-check"),
)// Randomize cache refresh to avoid stampede
j, _ := s.NewJob(
gocron.DurationRandomJob(
4*time.Minute,
6*time.Minute,
),
gocron.NewTask(func() {
if cache.IsStale() {
cache.Refresh()
}
}),
gocron.WithName("cache-refresh"),
)// Poll external API with randomized intervals to avoid rate limits
j, _ := s.NewJob(
gocron.DurationRandomJob(
45*time.Second,
75*time.Second, // Average 60s, but randomized
),
gocron.NewTask(func() {
data, err := externalAPI.FetchUpdates()
if err != nil {
logger.Error("API fetch failed", "error", err)
return
}
processUpdates(data)
}),
gocron.WithName("api-poller"),
)// Run every 30 seconds, but only for 5 minutes
start := time.Now()
end := start.Add(5 * time.Minute)
j, _ := s.NewJob(
gocron.DurationJob(30*time.Second),
gocron.NewTask(func() {
fmt.Println("Limited run")
}),
gocron.WithStartAt(gocron.WithStartDateTime(start)),
gocron.WithStopAt(gocron.WithStopDateTime(end)),
)// Wait 1 hour before starting, then run every 5 minutes
startTime := time.Now().Add(time.Hour)
j, _ := s.NewJob(
gocron.DurationJob(5*time.Minute),
gocron.NewTask(func() {
fmt.Println("Delayed interval job")
}),
gocron.WithStartAt(gocron.WithStartDateTime(startTime)),
)// Start immediately, then switch to regular interval
j, _ := s.NewJob(
gocron.DurationJob(time.Hour),
gocron.NewTask(func() {
syncData()
}),
gocron.WithStartAt(gocron.WithStartImmediately()),
gocron.WithName("data-sync"),
)
// Runs once now, then hourly// Very frequent checks (every 5 seconds)
j, _ := s.NewJob(
gocron.DurationJob(5*time.Second),
gocron.NewTask(func() {
metrics := collectMetrics()
if metrics.ErrorRate > 0.05 {
triggerAlert()
}
}),
gocron.WithName("metrics-check"),
gocron.WithSingletonMode(gocron.LimitModeReschedule),
)// Regular background processing (every 5 minutes)
j, _ := s.NewJob(
gocron.DurationJob(5*time.Minute),
gocron.NewTask(func() {
processBackgroundQueue()
}),
gocron.WithName("background-processor"),
)// Periodic cleanup (every 6 hours)
j, _ := s.NewJob(
gocron.DurationJob(6*time.Hour),
gocron.NewTask(func() {
cleanupTempFiles()
archiveOldLogs()
}),
gocron.WithName("maintenance"),
)// If job is running when next run is scheduled, skip it
j, _ := s.NewJob(
gocron.DurationJob(30*time.Second),
gocron.NewTask(func() {
time.Sleep(time.Minute) // Takes longer than interval
}),
gocron.WithSingletonMode(gocron.LimitModeReschedule),
)// If job is running, queue the next run
j, _ := s.NewJob(
gocron.DurationJob(30*time.Second),
gocron.NewTask(func() {
time.Sleep(time.Minute)
}),
gocron.WithSingletonMode(gocron.LimitModeWait),
)// Best for ensuring consistent rest periods between runs
j, _ := s.NewJob(
gocron.DurationJob(30*time.Second),
gocron.NewTask(func() {
// Variable duration work
processItems()
}),
gocron.WithIntervalFromCompletion(),
gocron.WithSingletonMode(gocron.LimitModeReschedule),
)package main
import (
"context"
"fmt"
"os"
"os/signal"
"syscall"
"time"
"github.com/go-co-op/gocron/v2"
)
func main() {
s, err := gocron.NewScheduler(
gocron.WithLocation(time.UTC),
gocron.WithLogger(gocron.NewLogger(gocron.LogLevelInfo)),
)
if err != nil {
panic(err)
}
defer s.Shutdown()
// Fast interval: every 10 seconds
s.NewJob(
gocron.DurationJob(10*time.Second),
gocron.NewTask(func() {
fmt.Println("Fast check")
}),
gocron.WithName("fast-check"),
gocron.WithSingletonMode(gocron.LimitModeReschedule),
)
// Medium interval: every 5 minutes
s.NewJob(
gocron.DurationJob(5*time.Minute),
gocron.NewTask(func(ctx context.Context) {
fmt.Println("Medium task")
time.Sleep(2 * time.Minute)
}),
gocron.WithName("medium-task"),
gocron.WithIntervalFromCompletion(),
)
// Slow interval with jitter: every 15-20 minutes
s.NewJob(
gocron.DurationRandomJob(15*time.Minute, 20*time.Minute),
gocron.NewTask(func() {
fmt.Println("Slow task with jitter")
}),
gocron.WithName("slow-task"),
)
// Hourly maintenance
s.NewJob(
gocron.DurationJob(time.Hour),
gocron.NewTask(func() {
fmt.Println("Hourly maintenance")
}),
gocron.WithName("hourly-maintenance"),
gocron.WithStartAt(gocron.WithStartImmediately()),
)
s.Start()
fmt.Println("Scheduler started with interval jobs")
// Print schedule
for _, j := range s.Jobs() {
nextRun, _ := j.NextRun()
fmt.Printf("%s: next run at %v\n", j.Name(), nextRun)
}
// Wait for interrupt
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM)
<-sigCh
fmt.Println("Shutting down...")
}DurationJob, DurationRandomJobWithIntervalFromCompletion, WithSingletonModeInstall with Tessl CLI
npx tessl i tessl/golang-github-com-go-co-op-gocron-v2docs
api
examples
guides