Framework Guide

Rails Uptime Monitoring

Set up health check endpoints and uptime monitoring for your Ruby on Rails application. Simple patterns for production-ready monitoring.

Why Monitor Your Rails App?

Rails applications rely on a complex stack of services in production -- ActiveRecord databases, Redis for caching and Sidekiq, Action Cable for WebSockets, and Active Storage for file uploads. A failure in any of these can leave your app partially broken while Puma happily keeps accepting connections.

  • Puma worker crashes -- A segfault in a native gem or OOM kill silently removes a worker, reducing capacity without any visible error
  • Database connection pool exhaustion -- ActiveRecord's connection pool can be drained under high concurrency, causing requests to queue and timeout
  • Sidekiq worker failures -- Background job processors can crash or disconnect from Redis without affecting web requests
  • Migration drift -- A failed or skipped migration leaves your schema out of sync with the running code

Simple Health Route

# config/routes.rb
Rails.application.routes.draw do
  get '/health', to: 'health#show'
  get '/ready', to: 'health#ready'
end

# app/controllers/health_controller.rb
class HealthController < ApplicationController
  skip_before_action :authenticate_user!, raise: false

  def show
    render json: { status: 'healthy', timestamp: Time.current.iso8601 }
  end

  def ready
    checks = {}
    healthy = true

    # Check database
    begin
      start = Time.current
      ActiveRecord::Base.connection.execute('SELECT 1')
      checks[:database] = {
        status: 'ok',
        response_time_ms: ((Time.current - start) * 1000).round(2)
      }
    rescue => e
      healthy = false
      checks[:database] = { status: 'error', message: e.message }
    end

    status_code = healthy ? :ok : :service_unavailable
    render json: {
      status: healthy ? 'healthy' : 'unhealthy',
      timestamp: Time.current.iso8601,
      checks: checks
    }, status: status_code
  end
end

Using Rails 7.1+ Built-in Health Check

# Rails 7.1+ has a built-in health check endpoint
# config/routes.rb
Rails.application.routes.draw do
  get "up" => "rails/health#show", as: :rails_health_check
end

# This returns 200 OK if the app is running
# Default path: /up

Comprehensive Health Check

# app/controllers/health_controller.rb
class HealthController < ApplicationController
  skip_before_action :authenticate_user!, raise: false
  skip_before_action :verify_authenticity_token

  def ready
    checks = {}
    healthy = true

    # Database check
    checks[:database] = check_database
    healthy = false unless checks[:database][:status] == 'ok'

    # Redis check
    checks[:redis] = check_redis
    healthy = false unless checks[:redis][:status] == 'ok'

    # Sidekiq check
    checks[:sidekiq] = check_sidekiq

    render json: {
      status: healthy ? 'healthy' : 'unhealthy',
      timestamp: Time.current.iso8601,
      checks: checks,
      version: ENV.fetch('APP_VERSION', 'unknown'),
      ruby_version: RUBY_VERSION,
      rails_version: Rails::VERSION::STRING
    }, status: healthy ? :ok : :service_unavailable
  end

  private

  def check_database
    start = Time.current
    ActiveRecord::Base.connection.execute('SELECT 1')
    {
      status: 'ok',
      response_time_ms: ((Time.current - start) * 1000).round(2)
    }
  rescue => e
    { status: 'error', message: e.message }
  end

  def check_redis
    start = Time.current
    Redis.current.ping
    {
      status: 'ok',
      response_time_ms: ((Time.current - start) * 1000).round(2)
    }
  rescue => e
    { status: 'error', message: e.message }
  end

  def check_sidekiq
    stats = Sidekiq::Stats.new
    {
      status: 'ok',
      enqueued: stats.enqueued,
      processed: stats.processed,
      failed: stats.failed
    }
  rescue => e
    { status: 'error', message: e.message }
  end
end

Using okcomputer Gem

# Gemfile
gem 'okcomputer'

# config/initializers/okcomputer.rb
OkComputer::Registry.register "database", OkComputer::ActiveRecordCheck.new
OkComputer::Registry.register "redis", OkComputer::RedisCheck.new(Redis.current)
OkComputer::Registry.register "sidekiq", OkComputer::SidekiqLatencyCheck.new('default', 100)

# Routes are automatically mounted at /okcomputer
# GET /okcomputer - all checks
# GET /okcomputer/database - specific check

Skip Authentication

# For Devise
class HealthController < ApplicationController
  skip_before_action :authenticate_user!
end

# For basic auth in production
# config/routes.rb
constraints(->(req) { req.path == '/health' }) do
  get '/health', to: 'health#show'
end

Best Practices

  • Skip authentication — Health checks need to work without auth
  • Check Sidekiq — Background jobs are critical for most Rails apps
  • Monitor migrations — Ensure database schema is current
  • Add timeouts — Use Timeout.timeout() for dependency checks

Common Rails Production Issues

ActiveRecord Connection Pool Exhaustion

Puma threads compete for database connections from ActiveRecord's pool. When pool size is less than the thread count, requests queue waiting for a connection. Set pool in database.yml equal to or greater than your Puma thread count. Your health check should verify a connection is available.

Long-Running Migrations

Adding indexes or altering columns on large tables can lock them for minutes or hours. During this time, any query touching that table hangs. External monitoring detects when your app becomes unresponsive during deploys. Consider using strong_migrations gem for safe migrations.

Asset Pipeline / Webpacker Failures

A failed asset precompilation or missing Webpacker manifest can leave your app serving pages without CSS or JavaScript. While API endpoints still work, users see a broken UI. Monitor both your API health endpoint and a front-end URL that loads assets.

Redis Connection Drops

Redis powers caching, sessions, Action Cable, and Sidekiq in many Rails apps. A Redis disconnection can cascade into multiple failures. Include a Redis ping in your readiness check and configure reconnection logic in your redis.rb initializer.

Frequently Asked Questions

Does Rails have a built-in health check endpoint?
Yes, Rails 7.1+ includes a built-in health check at /up that returns HTTP 200 if the app is running. It's mapped to rails/health#show in your routes. For older Rails versions, or if you need dependency checks (database, Redis, Sidekiq), create a custom HealthController with /health and /ready endpoints.
Should I use the okcomputer gem or write custom health checks?
For simple apps, a custom controller is sufficient. Use okcomputer when you need pre-built checks for ActiveRecord, Redis, Sidekiq, Elasticsearch, and more. It auto-mounts at /okcomputer and supports individual check endpoints. It's well-maintained and widely used in production Rails applications.
How do I skip Devise authentication for health check routes?
Add skip_before_action :authenticate_user! to your HealthController. If using other authorization gems (Pundit, CanCanCan), also skip their callbacks. For API-only Rails apps with token auth, ensure the health route is outside any authenticated scope in your routes.rb.
How do I monitor Sidekiq workers in my Rails health check?
Use Sidekiq::Stats.new to check enqueued, processed, and failed job counts. High enqueued counts or rising failure rates indicate worker issues. Also verify the Redis connection since Sidekiq depends on it. The okcomputer gem includes a SidekiqLatencyCheck that alerts when queue latency exceeds a threshold.
How often should I check my Rails application's health?
For production Rails apps, check every 1-5 minutes. UptimeSignal's free tier checks every 5 minutes, which catches most outages quickly. Pro tier supports 1-minute intervals for faster detection. Puma's built-in worker recycling handles most memory issues, but external monitoring catches full-app failures that process managers miss.

Monitor your Rails API

Add your health endpoint to UptimeSignal and get alerted when it fails. Free tier includes 25 monitors.

Start monitoring free →

No credit card required. Commercial use allowed.

More Framework Guides