Written by James Britt
Technically reviewed by Jim Freeze · Reviewed 3 September 2026
A Rails app can look as if it is running smoothly while failing to deliver something customers depend upon. The hosting dashboard is green, each basic uptime check passes and yet some critical tasks simply don’t get completed.
The company in this example is fictional, but the failure pattern is a familiar one in production Rails applications.
A Silent Failure After Deployment
On Tuesday at 10:14, a small subscription service deployed a routine change. The web process started normally. Customers could log in, see their account information and even edit their profiles. The hosting dashboard indicated that everything was green.
At 10:22, a broken Redis connection left the Sidekiq worker processes active but unable to complete their assigned work. Password-reset emails, receipts and webhook deliveries began piling up silently, invisible to anyone checking the website. At 10:47, a customer contacted support because a reset email had not arrived. That complaint was the first useful signal that something was wrong.
The developer involved now had to stop what they were doing, review logs, check the deployment time, measure queue delays and help support determine how long customers had experienced the problem. While repairing the issue might require only five minutes, finding what went wrong, identifying what it impacted and notifying customers took much longer.
That is when effective Ruby on Rails monitoring needs to begin. “Can visitors access the homepage?” is only one of several questions we need to answer.
Define What Healthy Looks Like
For most Rails apps, I’d like to see monitoring answer the following three questions:
- Is the Rails process running?
- Are application dependencies available when requested?
- Are asynchronous jobs still processing?
We do not need to create a single endpoint to handle these three questions. A fast low-cost liveness check can run regularly. More expensive deeper checks can run less frequently and evaluate a handful of key dependent resources required by the application for its primary purpose.
Keeping these checks separate makes an alert easier to interpret. If Rails responds but the dependency check fails, I am probably dealing with an application dependency rather than a non-responsive web process.
Start With Rails’ Built-In /up Endpoint
Rails includes a built-in health endpoint at /up. It returns a successful response when the application has booted without raising an exception and an error response otherwise. The route normally looks like this:
get "up" => "rails/health#show", as: :rails_health_check
According to the Rails API documentation, /up is defined as a default health check that reports whether the application successfully booted.
Keep it. It is a useful liveness signal for a load balancer or an external monitor. Just remember what it does not establish: a successful response from /up does not prove that the database accepts queries, Redis is accessible or background jobs are being processed.
Create Another Route to Monitor Dependencies
To test the dependencies used by the subscription service, add another route:
get "health", to: "health#show"
Your controller can test both your database and Redis without revealing internal implementation details:
class HealthController < ActionController::API
def show
checks = {
application: "ok",
database: database_status,
redis: redis_status
}
healthy = checks.values.all? { |value| value == "ok" }
render(
json: {
status: healthy ? "ok" : "degraded",
checks: checks
},
status: healthy ? :ok : :service_unavailable
)
end
private
def database_status
ActiveRecord::Base.connection.execute("SELECT 1")
"ok"
rescue StandardError
"failed"
end
def redis_status
Sidekiq.redis { |connection| connection.ping }
"ok"
rescue StandardError
"failed"
end
end
A typical JSON response should contain minimal data:
{
"status": "ok",
"checks": {
"application": "ok",
"database": "ok",
"redis": "ok"
}
}
If Redis isn’t reachable, the health check returns HTTP 503 Service Unavailable along with a generic “failed” state. An external monitor receives a valid signal but users don’t receive hostnames, credentials or raw exception messages.
Remove the Redis check if the application does not use Sidekiq. If another service is essential to the application’s core job, test that instead. Developers wishing to modify the ruby code shown here can refer to these real-world examples of Ruby code.
Avoid Making /health Overly Complex
A health endpoint can easily become a laundry list of all of your systems and services. I would recommend against that.
A single call to your app can contact multiple services. There are many potential points of failure. And, you’re adding unnecessary traffic during a period when one of those systems may be experiencing performance degradation.
It’s usually best to have two separate levels of checks:
- /up is an inexpensive, high-frequency liveness check for Rails.
- /health evaluates only the few dependency services needed for your application’s core operation.
Any optional third-party services can have their own custom checks. Otherwise, a slow analytics service can cause your entire Rails application to appear dead to customers while they are able to continue to use it.
Also avoid making the public response too verbose. Log files are meant to hold diagnostic details, not expose internal system configuration details to outsiders.
Verify That Background Jobs Are Processing
Another aspect of the initial example is that pinging Redis indicates that Rails can reach Redis. However, it does not indicate that any of the queued jobs are being processed.
One option for closing this gap is to implement a heartbeat job. Run a very small job every few minutes that records when it completes:
class WorkerHeartbeatJob < ApplicationJob
queue_as :default
def perform
Rails.cache.write(
"worker_heartbeat_at",
Time.current.iso8601,
expires_in: 15.minutes
)
end
end
The health check can verify whether or not this recorded timestamp matches what it expects based on how often this job should occur.
Queue length alone can be misleading. Five hundred jobs may be harmless if workers are clearing them rapidly. Twenty jobs can be serious if the oldest has waited for more than an hour. In practice, the age of unfinished work often tells you more than the number waiting.
Test Before Production
A health check that has only worked while everything else was functioning properly isn’t ready for production yet.
Write a basic request spec that verifies proper behavior:
RSpec.describe "Application health", type: :request do
it "returns an OK response" do
get "/health"
expect(response).to have_http_status(:ok)
expect(JSON.parse(response.body)["status"]).to eq("ok")
end
end
Test edge case scenarios next. Prevent database connections. Simulate Redis going offline. Simulate when a worker heartbeat goes stale. And confirm that if a response is slow enough, it will trigger a timeout-based alert.
Don’t aim to model every possible failure scenario. Instead try modeling the ones you care about most and verifying that they generate meaningful statuses and notify the correct team member.
Monitor From Outside Your App
An internal dashboard helps once somebody knows there is a problem. It is much less useful when the application or its network cannot be reached.
For both /up and /health endpoints, you should have an external monitor assess both the HTTP status and response time for those endpoints. Also consider assessing if it contains expected values in the response body for /health. Testing from multiple regions can help distinguish if you’re seeing an issue due to widespread service disruptions versus regional routing issues.
Include specific actionable items in any alert generated by monitoring. Include which endpoint(s) failed; which tests failed; when did it begin; was it validated through other means; and which customer-visible functions may be impacted.
“Down” isn’t a great starting point.
Use Your Health Check Output to Power Your Status Page
Monitoring enables detection for your team. Communication enables your customers.
During an incident, developers need to investigate the cause and restore service. They should not have to copy the same update into support replies, social posts and a manually maintained status page.
DevHelm is one service that bundles status pages with uptime monitoring this way — checks detect the problem, the status page reflects it, and subscribers get notified, all without someone on the team having to context-switch away from actually fixing things.
In the subscription-service example, that closes the loop: the failed check alerts the team, the status page displays the incident, and customers have a trusted place to look before sending another support request.
Use language that describes the customer impact rather than the plumbing:
Password-reset emails are delayed.
That is useful to a customer. “Redis connection pool degraded” is mainly useful to the engineer already investigating the cause.
Don’t Train the Team to Ignore Alerts
Simply creating additional checks does not inherently enhance Ruby on Rails monitoring. Creating noise-generating alarms which fire repeatedly for minor, self-healing anomalies trains staff to ignore similar future alarms.
Consider implementing confirmation windows wherever applicable. Send emergency notifications differently than informational notices. A broken checkout experience and an abnormally slow report should not trigger the same response.
After an incident, ask three questions: Did monitoring identify the problem? Was the warning early enough? Did the alert help the person responding?
If a customer still reports the fault first, the monitors are watching the wrong behaviour.
Production Monitoring Checklist
<summary>Open the Rails monitoring checklist</summary>
- Keep /up as a simple Rails liveness check.
- Add /health for a small number of essential dependencies.
- Return meaningful HTTP status codes.
- Keep sensitive diagnostic details out of public responses.
- Track background-job progress, not only Redis connectivity.
- Monitor from outside the Rails application.
- Confirm failures before sending disruptive alerts when appropriate.
- Describe customer impact clearly on the public status page.
- Test failure paths before depending on them.
- Review monitoring after every important incident.
Ask the Better Question
At 10:14, the deployment looked fine. At 10:22, an important part of the application stopped doing useful work. At 10:47, a customer supplied the signal that monitoring had missed.
Rails had not vanished. The team had measured the easiest behaviour instead of the behaviour customers relied upon.
“Is the site up?” is a fair first question. The better question is whether Rails booted, its essential dependencies work, background jobs are moving and customers can understand a disruption without chasing the development team.
When those signals are connected, a quiet failure becomes much harder to miss.
