Ruby

How to Monitor Cron Jobs in Ruby with Cronaman

April 27, 2026·6 min read

The silent failure problem

Ruby on Rails applications lean heavily on scheduled tasks — generating reports, sending digest emails, syncing external APIs, pruning old records. Most of these end up as entries in a crontab managed by the whenever gem, or as recurring Sidekiq jobs.

What they all have in common: when they stop running, nothing tells you. The exception gets swallowed, the crontab entry silently disappears after a deploy misconfiguration, and you find out days later when a customer complains about missing data.

What is heartbeat monitoring?

Heartbeat monitoring is a dead man's switch for scheduled jobs. You configure an expected interval in Cronaman — say, every 6 hours — and your job sends a simple HTTP ping at the end of every successful run. If that ping doesn't arrive within the window, Cronaman transitions the monitor to "late" then "down" and fires an alert.

There's no gem to add to your Gemfile, no agent to deploy. Ruby's standard library includes net/http, which is everything you need.

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., "Weekly digest") and set the interval to match your cron schedule
  • Copy your unique ping URL: https://cronaman.dev/ping/weekly-digest

Ping Cronaman with net/http (no dependencies)

Ruby's net/http handles the ping with no additional gems. Here's the minimal pattern:

digest_job.rb
require 'net/http'
require 'uri'

PING_URL  = 'https://cronaman.dev/ping/weekly-digest'
FAIL_URL  = 'https://cronaman.dev/ping/weekly-digest/fail'

def ping(url)
  uri = URI(url)
  Net::HTTP.start(uri.host, uri.port, use_ssl: true, open_timeout: 5, read_timeout: 5) do |http|
    http.get(uri.request_uri)
  end
rescue StandardError
  # Don't let a network error mask the original job failure
end

begin
  # Your job logic here
  WeeklyDigestMailer.deliver_all!
  ping(PING_URL)    # success
rescue => e
  $stderr.puts "Job failed: #{e.message}"
  ping(FAIL_URL)    # signal failure immediately
  raise
end

The rescue StandardError inside ping is intentional — if Cronaman is temporarily unreachable, you don't want a network error to replace the real exception. Always set both open_timeout and read_timeoutnet/http has no default timeout and will block indefinitely without them.

Integration with the whenever gem

If you use the whenever gem to manage your crontab, add the ping directly to your schedule.rb:

config/schedule.rb
every 1.week, at: '6:00 am', roles: [:app] do
  runner "WeeklyDigestJob.perform_now"
  # ping Cronaman on success — && means curl only runs if runner exits 0
  command "curl -fsS --max-time 10 https://cronaman.dev/ping/weekly-digest > /dev/null 2>&1"
end

For a cleaner approach, move the ping into the job class itself so it's always paired with the actual work:

app/jobs/weekly_digest_job.rb
class WeeklyDigestJob < ApplicationJob
  PING_URL = 'https://cronaman.dev/ping/weekly-digest'
  FAIL_URL = 'https://cronaman.dev/ping/weekly-digest/fail'

  def perform
    WeeklyDigestMailer.deliver_all!
    cronaman_ping(PING_URL)
  rescue => e
    cronaman_ping(FAIL_URL)
    raise
  end

  private

  def cronaman_ping(url)
    require 'net/http'
    uri = URI(url)
    Net::HTTP.start(uri.host, uri.port, use_ssl: true, open_timeout: 5, read_timeout: 5) do |h|
      h.get(uri.request_uri)
    end
  rescue StandardError
    # silently absorb network errors
  end
end

Integration with Sidekiq-cron

The same job class pattern works identically with sidekiq-cron. Define the schedule in your initializer:

config/initializers/sidekiq.rb
Sidekiq::Cron::Job.create(
  name:  'Weekly digest',
  cron:  '0 6 * * 1',   # every Monday at 6 AM
  class: 'WeeklyDigestJob'
)

Since the ping is inside the job class, it fires automatically on every Sidekiq execution. No schedule.rb changes needed.

Verify your setup

Run the job once from the Rails console to confirm the ping fires:

terminal
rails runner "WeeklyDigestJob.perform_now"

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

terminal
curl -v https://cronaman.dev/ping/weekly-digest

More cron monitoring guides

Using a different stack? The same pattern works everywhere:

Start monitoring your Ruby cron jobs

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

Start monitoring free