---
title: "Rails health check endpoint and /up | Logdash"
description: "Rails 8 mounts /up by default, but it only proves the app booted. Add a controller that queries the database, returns 503, and put a monitor on it."
url: https://logdash.io/health-check/rails
---

# Rails health check endpoint and what /up actually checks

Rails 8 already serves a health check at /up, but it only returns 200 when the app boots without raising, so if you want the database checked you write your own controller and return 503 when the query fails.

Rails has shipped a health check since 7.1, and every app generated on Rails 8 still has it. One line sits near the top of config/routes.rb: get "up" => "rails/health#show", as: :rails\_health\_check. Hit /up and Rails::HealthController renders a green page with a 200, or a red one with a 500 if something raised on the way in. You do not have to add it, wire it or name it. Most people running Rails in production already have this endpoint and have never opened it, which is worth knowing before you go shopping for a gem.

The next thing to know is what it does not do, and Rails is honest about this in the controller's own documentation: the endpoint does not reflect the status of your application's dependencies. It answers exactly one question. Did this process boot without raising. A Puma worker whose connection pool is full of dead Postgres sockets booted fine hours ago, and it will keep answering /up with a cheerful 200 while every real request 500s. That is the failure mode that costs you the outage. A monitor pointed at /up sits green through the whole thing, and a monitor you have learned to trust while it is wrong is worse than no monitor at all.

## The controller you write when the database matters

 app/controllers/health\_controller.rb 

```ruby
class HealthController < ApplicationController
  # Whatever ApplicationController runs before every action, skip it here.
  skip_before_action :authenticate_user!, raise: false

  def show
    ActiveRecord::Base.with_connection { |c| c.select_value("SELECT 1") }
    render json: { ok: true }
  rescue StandardError => e
    Rails.logger.warn("health check failed: #{e.class}")
    # 503, so a booted app with a dead database reads as down.
    render json: { ok: false, error: "database" }, status: :service_unavailable
  end
end
```

Then point the existing route at it in config/routes.rb: get "up" => "health#show", as: :rails\_health\_check. Keeping the path means the load balancer config, the Kubernetes probe and anything else already hitting /up carry on working without a change.

## What to check and what to leave out

- One query against the database you cannot serve a request without. SELECT 1 through the pool is enough: it proves the pool still hands out a live connection, and it costs well under a millisecond.
- Redis, only if a request genuinely fails without it. If the app degrades to a slower page, that is not something worth waking anyone up for.
- Nothing that calls a third-party API. Fan out to Stripe and S3 and you will page yourself at 3am for someone else's outage, about a thing you cannot fix.
- A budget well under a second. The Logdash pinger gives up after 10 seconds and records the check as down, and an endpoint that needs 10 seconds is already telling you something.
- No version string, no migration status, no environment dump in the body. Assume the URL is public, because eventually it is.

## Point a monitor at it

1. **Ship it, then try to break it** Deploy, open /up in a browser, then stop Postgres on your machine and load the same path. You want a small JSON body with a 503, not a 500 page with a stack trace in it.
2. **Create the monitor** Add a monitor in Logdash and paste the full URL. Checks run every 5 minutes on the free plan, every minute on Builder and every 15 seconds on Pro, and each one records the status code and the response time.
3. **Take the database away** Point staging at a dead database and wait one interval. The status code leaves the 200-399 range, the monitor flips to down on that transition, and the Telegram alert arrives naming the endpoint and the code it got back.

## The one that catches people: host authorization

If you have set config.hosts in production, ActionDispatch::HostAuthorization answers any request with an unexpected Host header with 403 Forbidden. An external monitor hitting your real domain is fine. A load balancer or a Kubernetes probe hitting the pod by IP is not, and 403 sits outside 200-399, so the check reads as down while the app is perfectly healthy. The Rails guide gives you the exclusion: config.host\_authorization = { exclude: ->(request) { request.path == "/up" } }. Set it before you lose an evening to a red monitor pointed at a green app.

### Does Rails 8 have a built-in health check endpoint? 

Yes. Every generated app routes /up to Rails::HealthController, which returns 200 if the app booted without raising and 500 if it did not. It has been the default since Rails 7.1, so most apps already have it and most owners have never looked.

### What does the Rails /up endpoint actually check? 

That the process is running and boots clean. Nothing else. It never touches the database, Redis or a queue, and the controller documentation says so directly, which is why a custom action is the answer as soon as the database matters.

### What status code should a Rails health check return? 

200 when the app can serve traffic, 503 when a dependency it needs is gone. Logdash marks a monitor down on anything outside 200-399, so the 503 is what turns the endpoint into an alert instead of a chart.

### Do I need a health check gem for Rails? 

Usually not. health\_check and okcomputer earn their place once you want a registry of named checks across a large app. For one controller and one SELECT 1, a gem is more configuration than code.

### Should a Rails health check endpoint be authenticated? 

Leave it open and keep the body boring. Monitors and load balancers cannot carry credentials easily, and a response of ok true leaks nothing. If it truly has to be private, restrict it by network rather than by password.

## Keep reading

[All health check guides](https://logdash.io/health-check): One page per framework, each with the endpoint code and the monitor that watches it.

[Laravel health check endpoint and the built-in /up route](https://logdash.io/health-check/laravel): Laravel 11 and 12 register a health route at /up out of the box, but it only proves the framework booted, so add a route that runs one database query and returns 503 when it fails.

[Elixir Phoenix health check endpoint with Ecto and the cluster](https://logdash.io/health-check/phoenix): Phoenix ships no health route, so you write a controller action that runs one Ecto query, returns 200 with the number of connected nodes, and answers 503 when the query raises.

[Spring Boot health check endpoint with Actuator](https://logdash.io/health-check/spring-boot): Add spring-boot-starter-actuator and Spring Boot serves /actuator/health for you, returning 200 while every contributor is UP and 503 the moment one of them goes DOWN.

[Uptime monitoring](https://logdash.io/features/monitoring): HTTP checks as often as every 15 seconds. When one fails, Telegram tells you before your users do.
