Turbo is the engine at the heart of Hotwire — it accelerates server-rendered HTML apps with Turbo Drive (fast page changes without full reloads), Turbo Frames (independent page fragments), and Turbo Streams (real-time server push over WebSocket or SSE). Together they let you ship rich, reactive interfaces without a heavy JavaScript framework. But Turbo's speed advantage vanishes the moment your Rails, Django, or Laravel backend is unavailable. Vigilmon gives you continuous HTTP uptime monitoring, WebSocket-aware health checks, and instant alerts when your Turbo-powered app goes down.
What You'll Build
- A
/healthendpoint covering database, Redis, and Action Cable - Vigilmon HTTP monitors for the app and the health endpoint
- A heartbeat for background jobs (Sidekiq, Celery, Horizon)
- Alert channels (email and Slack)
- An uptime badge in your Turbo layout
Prerequisites
- A Turbo-powered app (Rails with Hotwire, or any backend with
@hotwired/turbo) - A free Vigilmon account
Step 1: Add a Health Endpoint
Turbo Streams rely on Action Cable (or another WebSocket adapter) in addition to HTTP. Your health check should cover both layers.
Ruby on Rails (canonical Turbo stack)
# config/routes.rb
get "/health", to: "health#show"
# app/controllers/health_controller.rb
class HealthController < ApplicationController
skip_before_action :authenticate_user!, raise: false
def show
checks = {}
degraded = false
# Database
begin
ActiveRecord::Base.connection.execute("SELECT 1")
checks[:database] = "ok"
rescue => e
checks[:database] = "error: #{e.message}"
degraded = true
end
# Redis (used for Action Cable and Turbo Streams)
begin
Redis.current.ping
checks[:redis] = "ok"
rescue => e
checks[:redis] = "error: #{e.message}"
degraded = true
end
# Action Cable adapter
begin
ActionCable.server.config.cable.fetch("adapter")
checks[:action_cable] = "configured"
rescue => e
checks[:action_cable] = "error: #{e.message}"
degraded = true
end
render json: {
status: degraded ? "degraded" : "ok",
timestamp: Time.current.iso8601,
checks: checks
}, status: degraded ? :service_unavailable : :ok
end
end
Node.js backend with @hotwired/turbo
// server.js (Express)
import express from "express";
import { createClient } from "redis";
const app = express();
const redis = createClient({ url: process.env.REDIS_URL });
await redis.connect();
app.get("/health", async (req, res) => {
const checks = {};
let degraded = false;
try {
// await db.query("SELECT 1");
checks.database = "ok";
} catch (err) {
checks.database = `error: ${err.message}`;
degraded = true;
}
try {
await redis.ping();
checks.redis = "ok";
} catch (err) {
checks.redis = `error: ${err.message}`;
degraded = true;
}
res.status(degraded ? 503 : 200).json({
status: degraded ? "degraded" : "ok",
timestamp: new Date().toISOString(),
checks,
});
});
app.listen(process.env.PORT ?? 3000);
Test it:
curl http://localhost:3000/health | jq .
# {"status":"ok","timestamp":"2026-07-03T...","checks":{"database":"ok","redis":"ok","action_cable":"configured"}}
Step 2: Configure Vigilmon HTTP Monitors
Log in to Vigilmon → Monitors → New Monitor.
Monitor 1: App Root
| Field | Value |
|---|---|
| URL | https://yourapp.com/ |
| Method | GET |
| Expected status | 200 |
| Expected body | A stable string from your HTML (e.g. app title or data-turbo) |
| Check interval | 1 minute |
Monitor 2: Health Endpoint
| Field | Value |
|---|---|
| URL | https://yourapp.com/health |
| Method | GET |
| Expected status | 200 |
| Expected body | "status":"ok" |
| Check interval | 1 minute |
With two monitors, Vigilmon catches web server failures independently from database and Redis failures — which is critical for Turbo Streams since Redis failure kills real-time updates even when HTTP pages still load.
Step 3: Heartbeat for Background Workers
Turbo Streams often broadcast from background jobs. A Vigilmon heartbeat catches silent Sidekiq/Resque/GoodJob worker failures before users notice frozen updates.
Sidekiq example
# app/jobs/broadcast_job.rb
class BroadcastJob < ApplicationJob
queue_as :default
def perform(record_id)
record = MyRecord.find(record_id)
# Broadcast Turbo Stream update
Turbo::StreamsChannel.broadcast_replace_to(
"my_record_#{record.id}",
target: "record_#{record.id}",
partial: "records/record",
locals: { record: record }
)
# Ping heartbeat on success
if (url = ENV["VIGILMON_HEARTBEAT_URL"]).present?
Net::HTTP.get(URI(url))
end
rescue => e
Rails.logger.error "[BroadcastJob] Failed: #{e.message}"
raise # No heartbeat ping → Vigilmon alerts
end
end
Set up the heartbeat in Vigilmon:
- Monitors → New Heartbeat Monitor
- Expected interval: 2× your job frequency
- Copy the ping URL and add to your environment:
VIGILMON_HEARTBEAT_URL=https://vigilmon.online/api/heartbeat/xxxxxxxx
Step 4: Add an Uptime Badge to Your Turbo Layout
Add the badge to your application layout. Turbo Drive preserves your layout across navigations, so the badge appears on every page:
<!-- Rails: app/views/layouts/application.html.erb -->
<footer>
<a href="https://vigilmon.online/status/your-monitor-slug"
target="_blank"
rel="noopener noreferrer"
aria-label="Service uptime status">
<img src="https://vigilmon.online/api/badge/your-monitor-id.svg"
alt="Uptime"
width="120"
height="20">
</a>
</footer>
Because Turbo Drive only replaces <body> content (keeping <head> and persistent elements), you can also place the badge inside a data-turbo-permanent element so it survives every navigation:
<div data-turbo-permanent id="status-badge">
<a href="https://vigilmon.online/status/your-monitor-slug"
target="_blank" rel="noopener noreferrer">
<img src="https://vigilmon.online/api/badge/your-monitor-id.svg"
alt="Uptime" width="120" height="20">
</a>
</div>
Step 5: Set Up Alert Channels
In Vigilmon → Alert Channels:
- Alert Channels → Email → add your on-call address
- Attach to both HTTP monitors and the heartbeat
Slack
- Create a Slack Incoming Webhook for
#alerts - Alert Channels → Webhook → paste the URL
- Payload template:
{
"text": "🚨 *{{ monitor.name }}* is DOWN\n{{ monitor.url }}\nStatus: {{ event.status }}\n<https://vigilmon.online|Open Vigilmon>"
}
Step 6: Verify the Setup
# 1. Health endpoint returns 200
curl -s https://yourapp.com/health | jq .
# 2. Stop Redis → confirm health returns 503
# Expected: Vigilmon alerts within 2 minutes
# 3. Stop Sidekiq → wait for heartbeat window to expire
# Expected: heartbeat alert fires
# 4. Vigilmon → "Test Alert" → verify email/Slack delivery
Summary
| Monitor | What It Catches | |---|---| | HTTP root check | Web server and deployment failures | | HTTP health endpoint | Database, Redis, and Action Cable failures | | Heartbeat | Silent Sidekiq/worker failures, broken Turbo Streams |
Next Steps
- Add a dedicated Redis monitor for Turbo Streams uptime independent of the app health check
- Use Vigilmon's response time graphs to correlate Turbo Frame slow renders with database or job queue latency
- Set up multi-channel alerting: Slack for immediate, email for digest
Ready to get started? Sign up for Vigilmon free — have your first monitor running in under 5 minutes.