How to Monitor Cron Jobs in Ruby with Cronaman
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:
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
endThe 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_timeout — net/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:
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"
endFor a cleaner approach, move the ping into the job class itself so it's always paired with the actual work:
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
endIntegration with Sidekiq-cron
The same job class pattern works identically with sidekiq-cron. Define the schedule in your initializer:
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:
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:
curl -v https://cronaman.dev/ping/weekly-digestMore cron monitoring guides
Using a different stack? The same pattern works everywhere:
- How to Monitor Cron Jobs in Python — covers urllib and the requests library
- How to Monitor Cron Jobs in PHP — covers file_get_contents, cURL, and Laravel schedulers
- How to Monitor Cron Jobs in Laravel — covers Laravel Task Scheduling onSuccess/onFailure hooks
More guides
What Is Cron Job Monitoring? (And Why You Need It)
5 min readProBeyond Timing: Catch Silent Successes with Semantic Ping Payloads
5 min readPythonHow to Monitor Cron Jobs in Python with Cronaman
6 min readNode.jsHow to Monitor Cron Jobs in Node.js with Cronaman
7 min readPHPHow to Monitor Cron Jobs in PHP with Cronaman
6 min readBashHow to Monitor Cron Jobs in Bash with Cronaman
5 min readGoHow to Monitor Cron Jobs in Go with Cronaman
6 min readLaravelHow to Monitor Laravel Cron Jobs with Cronaman
6 min readStart 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