ruby 64 lines · 3 tabs

Rails Health-Check Endpoint With a Controller Concern and Database Ping

Shared by codesnips Sep 2026
3 tabs
module HealthCheckable
  extend ActiveSupport::Concern

  CheckResult = Struct.new(:name, :ok, :message, keyword_init: true)

  private

  def run_checks
    [check_database, check_redis]
  end

  def check_database
    safe_check("database") do
      ActiveRecord::Base.connection.execute("SELECT 1")
      true
    end
  end

  def check_redis
    safe_check("redis") do
      Redis.current.ping == "PONG"
    end
  end

  def safe_check(name)
    ok = yield
    CheckResult.new(name: name, ok: ok, message: ok ? "ok" : "unexpected response")
  rescue StandardError => e
    CheckResult.new(name: name, ok: false, message: e.message)
  end
end
3 files · ruby Explain with highlit

A health-check endpoint is the contract between a Rails app and whatever is watching it — a load balancer, a Kubernetes kubelet, or an uptime monitor. The tricky part is distinguishing liveness (the process is up and answering) from readiness (the process can actually serve traffic because its dependencies are reachable). This snippet separates those concerns and keeps the probe logic reusable.

The HealthCheckable concern centralizes the individual checks so any controller can mix them in. check_database runs a trivial SELECT 1 through ActiveRecord::Base.connection.execute, which is the cheapest way to confirm the connection pool can hand out a live, authenticated connection rather than just confirming the config parses. check_redis issues a ping and treats the string reply as truth. Each check is wrapped in safe_check, which rescues StandardError, records the failure message, and never lets one dependency's exception blow up the whole probe — an important property, because a health endpoint that 500s is useless to the very systems polling it.

In HealthController, liveness returns 200 unconditionally and does no I/O; it answers the question "should this pod be restarted?" and must stay fast and dependency-free. readiness runs the full check set via run_checks and returns 503 Service Unavailable when any dependency is down, which is exactly the signal a load balancer needs to stop routing traffic without killing the process. The response body is JSON listing per-check status, so an operator can see which dependency failed. skip_before_action :authenticate_user! and skip_forgery_protection keep probes unauthenticated, since orchestrators cannot present credentials.

The routes tab wires clean paths under a health namespace, mapping up to liveness and ready to readiness — mirroring common Kubernetes probe conventions.

A key trade-off: readiness checks add latency and load, so they should be lightweight and never cascade (avoid probing a dependency's dependencies). A subtle pitfall is running readiness on a path that itself requires the database via session lookups or auth middleware; skipping those callbacks avoids a probe that fails for the wrong reason. This pattern is worth reaching for the moment an app runs behind any orchestrator that makes traffic and restart decisions based on HTTP status.


Related snips

Share this code

Here's the card — post it anywhere.

Rails Health-Check Endpoint With a Controller Concern and Database Ping — share card
Link copied