Ruby on Rails still powers a huge share of production web software — from Shopify-scale platforms to thousands of quiet B2B apps earning real revenue. And a Rails deployment is never just one process: it is Puma workers, a Postgres or MySQL database, Redis, a fleet of Sidekiq or Solid Queue workers, a job scheduler, and precompiled assets served from somewhere. Any one of those can fail while the homepage keeps rendering, which means "is the site up?" tells you far less than you think.
This guide walks through monitoring a Rails app the way it actually breaks. We will start with the built-in /up endpoint and its blind spots, build a real health check, enumerate the Rails-specific failure modes worth alerting on, cover background jobs, and finish with a concrete CronAlert setup you can copy. CronAlert is agentless and runs on Cloudflare's edge, so everything here is just HTTP checks, content assertions, and heartbeats — nothing to install inside your app server.
Start with /up — then go deeper
Rails 7.1 introduced a built-in health endpoint: Rails::HealthController mounted at /up, included in the routes of every new app. It returns a 200 with a green page if the app boots without raising, and a 500 if initialization is broken. That makes it a perfect first monitor — it catches bad deploys, broken initializers, and crashed processes, and it costs you nothing to expose.
But read the comment Rails itself puts in routes.rb: /up deliberately does not check the database, cache, or queues. A Rails app whose Postgres is unreachable, whose connection pool is exhausted, or whose Redis has vanished will happily return 200 from /up while every real request 500s. Treat /up as a liveness check, not a health check. For the general pattern, see our guide to HTTP health check endpoints.
For a dependency-aware signal, add a deeper endpoint. The fastest path is a gem: okcomputer mounts a configurable endpoint with built-in checks for ActiveRecord, the Rails cache, Redis, and Sidekiq, and returns a non-200 when any registered check fails. The alternative is a small custom controller action that runs ActiveRecord::Base.connection.execute("SELECT 1"), does a quick Rails.cache write-and-read, pings Redis if your app cannot live without it, and returns 200 or 503 accordingly. Wrap each check in a rescue so a failing dependency produces a clean 503 rather than an unhandled exception. Keep it fast, keep the body small, and if the endpoint exposes diagnostic detail, protect it with a static token your monitor sends in a header. Our deep-dive on building a database health endpoint covers the query-level details.
What to monitor in a Rails app
One health endpoint is the foundation, not the whole story. Spread a handful of checks across the surfaces your users actually touch:
- The public site and homepage. Checked for a 200 and for real rendered content — not just that something responded.
- The built-in
/upendpoint. Your process-liveness signal; it catches deploys that fail to boot. - Your deep health endpoint. The dependency-aware check that verifies the database, cache, and Redis.
- Key API endpoints. If you serve an API, your most important read endpoints deserve their own checks with JSON assertions; see monitoring API endpoints.
- The sign-in page. Devise or not, if
/users/sign_instops rendering, your users are locked out even while the marketing pages look fine. - Asset availability. A direct request to a fingerprinted CSS or JS URL confirms
assets:precompileran and your CDN is serving the build. - HTTPS and the SSL certificate. An expired certificate takes the whole site down for browsers regardless of app health.
All of this fits comfortably inside CronAlert's free plan — 25 monitors at a 3-minute interval with SSL and content checks included. Pro tightens the interval to 1 minute and unlocks keyword monitoring across every monitor.
Rails-specific failure modes
Generic uptime advice misses the things that actually take Rails apps down. Design your checks around these:
- Connection pool exhaustion. Under load or with a leak, threads wait on the ActiveRecord pool until
ActiveRecord::ConnectionTimeoutErrorstarts flying. A health check that runs a real query catches it;/updoes not. - Pending migrations. A deploy that ships new schema but skips
rails db:migrateraisesActiveRecord::PendingMigrationErroror 500s on the views that touch changed tables — often while the homepage stays fine. - Blocked hosts. Rails 6+ host authorization returns a 403 "Blocked hosts" for any Host header missing from
config.hosts— a classic surprise when monitoring a new domain or hitting the app by IP. - Asset precompilation failures. If
assets:precompilefails or the CDN misses the new fingerprints, pages return 200 but render unstyled or with dead JavaScript. - Dead Sidekiq or Solid Queue workers. When the worker process dies, jobs pile up in Redis or the database and silently stop completing while HTTP traffic carries on as normal.
- A silent scheduler. sidekiq-cron, sidekiq-scheduler, or Solid Queue recurring tasks can stop firing after a config error or a bad deploy — nothing errors, work just stops happening.
- Puma worker timeouts and memory bloat. Slow requests that exceed the worker timeout get killed mid-flight, surfacing as intermittent 502s/504s and creeping response times; bloated workers get OOM-killed the same way.
- Redis down. When Redis backs your cache, sessions, and queues at once, losing it degrades or breaks everything — while
/upkeeps returning 200.
Notice how many of these never change the homepage status code. That is the central lesson: status codes alone lie, and you need content assertions and dependency-aware checks to see the truth.
Monitoring Sidekiq, Solid Queue, and scheduled jobs
Background processing is where Rails monitoring most often falls short. Your site can be flawlessly up while every Sidekiq worker has crashed or Solid Queue's dispatcher has stalled — and no HTTP check will notice, because nobody is making a request that fails. Emails stop sending, exports stop generating, cleanup jobs stop running, and you find out from a customer days later.
The fix is heartbeat monitoring, sometimes called dead-man's-switch monitoring. Instead of CronAlert reaching into your worker, your job reaches out: a lightweight recurring job makes a quick HTTP request to a unique CronAlert heartbeat URL every time it completes successfully. You tell CronAlert how often to expect the ping plus a grace period, and if a ping fails to arrive on schedule, CronAlert alerts you that the pipeline went silent. The contract is inverted from an uptime check — silence is the alarm.
A practical setup: schedule a tiny job on the same cadence as your most critical periodic work — via sidekiq-cron, sidekiq-scheduler, Solid Queue recurring tasks, or a whenever-managed cron entry — whose only duty is to confirm it can reach Redis and the database, then ping the heartbeat. If that ping stops landing, the whole background tier is in trouble. Our guides to cron job heartbeat monitoring and background worker monitoring walk through the patterns in depth, and for one-shot scheduled work see batch job monitoring. Heartbeat checks are available on every CronAlert plan, including free.
Catching "up but wrong" with content checks
A Rails response can be a perfect HTTP 200 and still be completely broken. A deploy that shipped an empty layout, a page rendering a styled "something went wrong" screen from your error handler, or a homepage whose assets 404ed will all pass a status-code check. To catch these you have to assert on what the page actually contains.
The simplest tool is a keyword assertion: pick a string that appears only on a correctly rendered page — a navigation label, a product name, your footer copyright line — and require the check to find it. If a broken deploy strips that content or swaps in an error screen, the keyword disappears and the check fails even though the status is 200. You can also assert that an error string is absent, so a leaked stack trace or a default error page trips the alarm. Our keyword monitoring guide covers building robust assertions, including regex matching on Pro.
For pages that should be byte-for-byte stable — a pricing page, terms of service, a generated status snapshot — a SHA-256 content hash is even stronger: CronAlert hashes the rendered output and alerts the instant it changes, catching both drift and defacement. See content monitoring for how content-hash checks work in practice.
SSL and response-time thresholds
Two more checks round out a Rails setup. First, certificate monitoring: with config.force_ssl on (as it should be), an expired TLS certificate takes the site down for every browser no matter how healthy Puma is — and renewals fail more often than people expect. Point an SSL check at your domain and get warned days before expiry; see SSL certificate monitoring.
Second, response-time thresholds. Rails endpoints that creep slower — an N+1 introduced without includes, an unindexed lookup, a degraded cache — are an early warning well before they become Puma timeouts and 502s. Configure a response-time threshold on your most important monitors so a view that crosses, say, two seconds alerts you while it is still merely slow. Our guide to timeout thresholds explains how to pick sensible numbers without generating noise.
A concrete CronAlert setup
Here is an end-to-end setup you can replicate in a few minutes after you create a free CronAlert account:
- Monitor
/up. Point an HTTP monitor athttps://yourapp.com/up, expect a 200, and set the interval (3 minutes free, 1 minute on Pro). - Add a deep health endpoint and monitor it. Use
okcomputeror a custom action that checks ActiveRecord, the cache, and Redis, and returns 200/503. Give it its own monitor. - Add a keyword assertion to the homepage. Require a string only present when the page renders correctly, so a broken deploy fails the check.
- Add API and sign-in monitors. Check a key API endpoint and your sign-in page so integrators and users are covered.
- Add a heartbeat for background jobs. Create a heartbeat monitor, set the expected interval and grace period, and have a recurring Sidekiq or Solid Queue job ping its URL on every successful run.
- Turn on SSL and response-time checks. Enable certificate monitoring on your domain and set a response-time threshold on your critical monitors.
- Wire up alert channels. Connect email, Slack, Discord, Teams, Telegram, PagerDuty, Opsgenie, Splunk On-Call, a webhook, or PWA push — so the right people hear about it the right way.
Every plan includes the full REST API, and CronAlert ships an MCP server so you can manage monitors directly from Claude Code, Cursor, or Windsurf. Teams that need multi-region coverage can move to the Team plan, which checks from five edge regions with quorum so a single regional blip never pages you.
Frequently asked questions
Does Rails have a built-in health check endpoint?
Yes. Rails 7.1+ ships Rails::HealthController at /up, wired into new apps by default. It returns 200 if the app boots without raising — but it deliberately does not check the database, cache, or queues, so treat it as a liveness check and add a deeper endpoint for real dependencies.
Should my Rails health check verify the database?
Yes. Run a trivial SELECT 1 through ActiveRecord so an unreachable database or exhausted connection pool surfaces as a failure. Pool exhaustion, paused instances, and pending migrations all leave /up returning 200 while real requests fail. Gems like okcomputer or health_check provide ready-made checks for the database, cache, Redis, and Sidekiq.
How do I monitor Sidekiq, Solid Queue, or scheduled Rails jobs?
Use heartbeat monitoring. Schedule a lightweight recurring job that pings a CronAlert heartbeat URL on every successful run, and configure the expected interval plus a grace period. If workers die, the queue stalls, or the scheduler stops firing, the ping stops arriving and CronAlert alerts you — the failure mode HTTP checks miss entirely.
Why does my Rails app return 403 Blocked hosts to a monitor?
That is ActionDispatch::HostAuthorization: Rails 6+ rejects requests whose Host header is not in config.hosts with a 403. Add every hostname you monitor to config.hosts, and treat a sudden wave of 403s after a deploy or DNS change as a configuration regression, not an outage.
Can uptime monitoring catch a broken Rails deploy that still returns 200?
Yes, with keyword and content checks. Failed asset precompiles, empty templates, and styled error pages can all return 200. A keyword assertion requiring a string only present on a correctly rendered page fails the check when real content is missing, and a SHA-256 content hash catches any drift on pages that should never change.
Start monitoring your Rails app
Rails apps fail in ways a single homepage ping will never catch: exhausted connection pools, pending migrations, dead Sidekiq workers, silent schedulers, and broken deploys that still answer 200. The built-in /up endpoint plus a deep health check, content assertions, heartbeats for background jobs, and SSL and response-time checks together give you a signal you can trust. CronAlert covers all of it agentlessly from the edge, and the free plan is enough to wire up your first complete setup today. Create a free CronAlert account and point your first check at /up in the next five minutes.
Related reading: Building a database health endpoint, Background worker monitoring, API endpoint monitoring, uptime monitoring for Django apps and for Laravel apps if you run polyglot stacks, and Redis monitoring.