Go

How to Monitor Cron Jobs in Go with Cronaman

April 27, 2026·6 min read

The silent failure problem

Go services often ship with built-in schedulers — goroutines that wake on a ticker, or jobs managed by robfig/cron. They're lightweight, easy to deploy, and completely invisible when they fail.

A panic in a goroutine that isn't recovered will silently kill just that goroutine while the rest of your service keeps running. A job that returns an error with no alerting path might log a line and keep moving. Days later you discover a critical sync stopped working — because nothing told you it failed.

What is heartbeat monitoring?

Heartbeat monitoring adds an external watchdog to your jobs. You configure an expected interval in Cronaman, and your job sends a simple HTTP ping at the end of every successful execution. If that ping doesn't arrive within the window, Cronaman alerts you.

Go's net/http package handles everything. No third-party SDK, no new dependencies.

Create a Cronaman monitor

Before writing any code, create a monitor in Cronaman:

  • Sign up at cronaman.dev — free plan, no credit card required
  • Click New Monitor
  • Name it (e.g., "Data sync") and set the interval to match your job's schedule
  • Copy your unique ping URL: https://cronaman.dev/ping/data-sync

Ping Cronaman with net/http

A small helper function handles the ping and swallows network errors so they never mask the real job error:

job.go
package main

import (
	"context"
	"fmt"
	"net/http"
	"time"
)

const (
	pingURL = "https://cronaman.dev/ping/data-sync"
	failURL = "https://cronaman.dev/ping/data-sync/fail"
)

var httpClient = &http.Client{Timeout: 10 * time.Second}

func pingCronaman(url string) {
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()
	req, _ := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
	resp, err := httpClient.Do(req)
	if err != nil {
		return // don't let a network error mask the job result
	}
	resp.Body.Close()
}

func runDataSync() error {
	// Your job logic here
	fmt.Println("Syncing data...")
	return nil
}

func job() {
	if err := runDataSync(); err != nil {
		fmt.Printf("job failed: %v\n", err)
		pingCronaman(failURL) // signal failure immediately
		return
	}
	pingCronaman(pingURL) // signal success
}

A few details worth noting:

  • Always set a Timeout on the HTTP client — Go's default client has no timeout and will block indefinitely
  • Reuse a single http.Client across calls to take advantage of connection pooling
  • Always close resp.Body even when you don't read it — otherwise connections leak

Integration with robfig/cron

If you use robfig/cron to schedule jobs inside your Go service, wrap your job function so the ping is always paired with the actual work:

scheduler.go
package main

import (
	"fmt"
	"github.com/robfig/cron/v3"
)

func main() {
	c := cron.New()

	c.AddFunc("0 2 * * *", func() { // daily at 02:00
		if err := runDataSync(); err != nil {
			fmt.Printf("data sync failed: %v\n", err)
			pingCronaman(failURL)
			return
		}
		pingCronaman(pingURL)
	})

	c.Start()
	// block forever
	select {}
}

Each cron.AddFunc callback runs in its own goroutine. Panics in that goroutine don't propagate to the scheduler, so wrap critical jobs with a recover to ensure the fail ping still fires:

scheduler_safe.go
c.AddFunc("0 2 * * *", func() {
	defer func() {
		if r := recover(); r != nil {
			fmt.Printf("panic in data sync: %v\n", r)
			pingCronaman(failURL)
		}
	}()

	if err := runDataSync(); err != nil {
		fmt.Printf("data sync failed: %v\n", err)
		pingCronaman(failURL)
		return
	}
	pingCronaman(pingURL)
})

Verify your setup

Call the job function directly from a test or main to confirm the ping fires:

terminal
go run . --run-job-once

Open your Cronaman dashboard. Within a few seconds the monitor should show "healthy" with a "Last ping" timestamp. If it stays grey, verify outbound HTTPS from your host:

terminal
curl -v https://cronaman.dev/ping/data-sync

More cron monitoring guides

Using a different stack? The same heartbeat pattern works everywhere:

Start monitoring your Go cron jobs

Free forever for up to 3 monitors. No credit card required. Set up in under 2 minutes.

Start monitoring free