DEV Community

Vigilmon
Vigilmon

Posted on

Uptime Monitoring for Railway Apps (Free, Multi-Region)

Uptime Monitoring for Railway Apps (Free, Multi-Region)

Railway is a deployment platform that makes it genuinely fast to go from code to running container. You push, it builds, it deploys. No Kubernetes YAML, no Dockerfile wrestling (usually), no CloudFormation.

But Railway does not send you an alert when your service goes down. That part is on you.

Here's how to add health checks and external uptime monitoring to a Railway-deployed service.


What breaks on Railway and why you need monitoring

Sleeping services — Railway's hobby plan sleeps services after 10 minutes of inactivity (on the legacy free tier). The first request after sleep takes 5–30 seconds to cold-start. Your monitoring will catch this as a timeout, which is exactly what it is.

Container restarts — Railway will restart a crashing container, but there's a window between crash and restart where requests return 503. Without monitoring, you only know this happened if a user complains.

Build failures on redeploy — If a new build fails, Railway rolls back to the last good deploy. But if you had a bad migration that ran before the rollback, your database might be in an inconsistent state. A health check that verifies database connectivity will catch this faster than your users will.

Environment variable gaps — Railway's service-to-service references (${{Postgres.DATABASE_URL}}) are evaluated at deploy time. If a referenced service restarts and changes its URL, your app may still be running with a stale connection string.


Step 1: Add a health endpoint

For a Node.js service:

// src/routes/health.ts
import { Router } from 'express'
import { Pool } from 'pg'

const router = Router()

// Railway sets DATABASE_URL automatically for Postgres services
const pool = process.env.DATABASE_URL
  ? new Pool({ connectionString: process.env.DATABASE_URL })
  : null

router.get('/health', async (req, res) => {
  const checks: Record<string, any> = {}

  // Check Postgres if configured
  if (pool) {
    const start = Date.now()
    try {
      await pool.query('SELECT 1')
      checks.database = { status: 'ok', latencyMs: Date.now() - start }
    } catch (err: any) {
      checks.database = { status: 'error', error: err.message }
    }
  }

  // Add Redis check if configured (Railway Redis service)
  if (process.env.REDIS_URL) {
    const { createClient } = await import('redis')
    const redis = createClient({ url: process.env.REDIS_URL })
    const start = Date.now()
    try {
      await redis.connect()
      await redis.ping()
      await redis.disconnect()
      checks.redis = { status: 'ok', latencyMs: Date.now() - start }
    } catch (err: any) {
      checks.redis = { status: 'error', error: err.message }
    }
  }

  const allOk = Object.values(checks).every((c: any) => c.status === 'ok')

  res.status(allOk ? 200 : 503).json({
    status: allOk ? 'ok' : 'degraded',
    service: process.env.RAILWAY_SERVICE_NAME ?? 'unknown',
    environment: process.env.RAILWAY_ENVIRONMENT_NAME ?? 'unknown',
    checks,
    uptime: process.uptime(),
  })
})

export default router
Enter fullscreen mode Exit fullscreen mode

Railway automatically injects RAILWAY_SERVICE_NAME and RAILWAY_ENVIRONMENT_NAME, so your health response tells you exactly which environment is being probed.


Step 2: Wire it up in your main app file

// src/app.ts
import express from 'express'
import healthRouter from './routes/health'

const app = express()
app.use(healthRouter)
// ... rest of your routes
app.listen(process.env.PORT || 3000)
Enter fullscreen mode Exit fullscreen mode

Railway sets PORT automatically — always use process.env.PORT, not a hardcoded value.


Step 3: Add a Procfile or healthcheck command

Railway supports a HEALTHCHECK in your Dockerfile:

HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
  CMD curl -f http://localhost:${PORT}/health || exit 1
Enter fullscreen mode Exit fullscreen mode

This tells Railway itself to restart the container if the health check fails three times in a row. It's separate from external monitoring — the container check handles restarts, the external monitor handles alerting you.


Step 4: Get the public URL

Railway generates a public domain for each service. Find it in the Railway dashboard under Settings → Networking → Public Networking. It looks like your-service.up.railway.app.


Step 5: Set up external monitoring

Your Dockerfile HEALTHCHECK only restarts the container. It doesn't tell you when it restarted or how long it was down. You need an external monitor for that.

  1. Go to vigilmon.online — free tier, no credit card.
  2. Create an HTTP(S) monitor.
  3. URL: https://your-service.up.railway.app/health
  4. Interval: 60s
  5. Expected status: 200
  6. Regions: pick two or more for multi-region coverage
  7. Alerts: email or Slack webhook

Step 6: Handle sleeping services (hobby plan)

If you're on Railway's hobby plan with sleeping enabled, configure your monitor to allow a 30-second timeout for the first probe after a sleep period. You'll still get alerted if the service fails to wake within that window, but you won't get false positives every time it sleeps.

Alternatively, use the monitoring pings themselves as keep-alive heartbeats: a 60-second monitor interval is enough to prevent Railway from putting the service to sleep.


Sample healthy response

{
  "status": "ok",
  "service": "api",
  "environment": "production",
  "checks": {
    "database": { "status": "ok", "latencyMs": 12 },
    "redis": { "status": "ok", "latencyMs": 3 }
  },
  "uptime": 86412.7
}
Enter fullscreen mode Exit fullscreen mode

Sample degraded response (503)

{
  "status": "degraded",
  "service": "api",
  "environment": "production",
  "checks": {
    "database": { "status": "error", "error": "connect ECONNREFUSED 127.0.0.1:5432" },
    "redis": { "status": "ok", "latencyMs": 2 }
  },
  "uptime": 86412.7
}
Enter fullscreen mode Exit fullscreen mode

Recap

  1. Add a /health endpoint that checks Postgres and Redis and returns structured JSON.
  2. Use RAILWAY_SERVICE_NAME and RAILWAY_ENVIRONMENT_NAME in your response — alerts will tell you which environment is failing.
  3. Add a HEALTHCHECK to your Dockerfile so Railway auto-restarts unhealthy containers.
  4. Point a free external monitor at your public Railway URL — vigilmon.online.
  5. On the hobby plan, a 60s monitoring interval doubles as a keep-alive that prevents the service from sleeping.

Railway handles the deploy pipeline. You handle knowing when the running service has a problem.

Top comments (0)