tutorial

How to Monitor Backstage Developer Portal with Vigilmon (HTTP, TCP, and Alerts)

Backstage is Spotify's open-source developer portal, now a CNCF incubating project adopted by Spotify, Lyft, American Airlines, and hundreds of engineering o...

Backstage is Spotify's open-source developer portal, now a CNCF incubating project adopted by Spotify, Lyft, American Airlines, and hundreds of engineering organizations worldwide. It centralises your software catalog, service docs, scaffolded templates, and TechDocs into a single pane of glass for your entire engineering team. When Backstage goes down, engineers lose access to the software catalog, internal tooling links, and on-call runbooks — and new services can't be scaffolded until the portal recovers.

In this tutorial you'll set up end-to-end monitoring for a self-hosted Backstage deployment using Vigilmon — free tier, no credit card required.


Why Backstage needs external monitoring

Backstage is a Node.js application backed by a PostgreSQL database and optionally several third-party integrations (GitHub, PagerDuty, Kubernetes). A typical production deployment involves:

  • Backstage app server — the main Node.js process serving the React frontend and backend APIs on port 7007
  • PostgreSQL — stores catalog entities, user sessions, and scaffolder task history
  • Catalog ingestion workers — periodically sync entities from GitHub, GitLab, or Bitbucket
  • Optional TechDocs builder — generates and serves MkDocs-based documentation

Failure modes that internal health checks cannot catch:

  • App server crashes silently — the process exits but the container is marked "running" because the restart policy brings it back; during restart, all API requests return 502
  • PostgreSQL connection pool exhaustion — the app is up but the database connection pool is full; entity queries fail with timeout errors
  • Catalog sync failure — the GitHub integration's token expires; services disappear from the catalog without any visible error on the homepage
  • Memory leak under heavy load — the Node.js heap grows until OOM kill, causing intermittent restarts during peak usage
  • Reverse proxy misconfiguration — a TLS renewal or nginx reload leaves the port temporarily unreachable even though the backend is healthy

An external monitor probing Backstage from outside your network is the only reliable way to detect all of these before your engineers report them via Slack.


What you'll need

  • A running Backstage deployment (Docker Compose, Kubernetes, or bare metal)
  • A free Vigilmon account — takes 30 seconds to create

Step 1: Verify Backstage health endpoints

Backstage ships with a built-in health endpoint provided by the @backstage/backend-defaults package. It's available at:

GET http://<your-host>:7007/healthcheck

A healthy Backstage instance returns:

{"status":"ok"}

Test it from outside your network first:

curl -f https://backstage.yourcompany.com/healthcheck
# Expected: {"status":"ok"}

If the health endpoint returns 404, you may be running an older Backstage version. Add the health check manually in your packages/backend/src/index.ts:

import { createServiceBuilder } from '@backstage/backend-common';
import Router from 'express-promise-router';

const router = Router();
router.get('/healthcheck', (_req, res) => {
  res.json({ status: 'ok', timestamp: new Date().toISOString() });
});

For Backstage deployed behind a reverse proxy, make sure /healthcheck is proxied through to the backend port:

server {
    listen 443 ssl;
    server_name backstage.yourcompany.com;

    ssl_certificate     /etc/letsencrypt/live/backstage.yourcompany.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/backstage.yourcompany.com/privkey.pem;

    location / {
        proxy_pass http://localhost:7007;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        # Required for Backstage WebSockets (TechDocs live reload)
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}

Step 2: Add a deep health check for catalog and database

The default /healthcheck only tells you the HTTP server is accepting connections. Add a deeper check that verifies the database connection and at minimum one catalog query:

// packages/backend/src/plugins/healthcheck.ts
import { Router } from 'express';
import { DatabaseManager } from '@backstage/backend-common';

export async function createHealthRouter(options: {
  database: DatabaseManager;
}): Promise<Router> {
  const router = Router();

  router.get('/health/deep', async (_req, res) => {
    try {
      // Verify database connectivity
      const client = await options.database.forPlugin('catalog').getClient();
      await client.raw('SELECT 1');

      res.json({
        status: 'ok',
        database: 'connected',
        timestamp: new Date().toISOString(),
      });
    } catch (err) {
      res.status(503).json({
        status: 'error',
        database: 'unreachable',
        error: err instanceof Error ? err.message : 'unknown',
      });
    }
  });

  return router;
}

Register the router in packages/backend/src/index.ts:

import { createHealthRouter } from './plugins/healthcheck';

const health = await createHealthRouter({ database });
apiRouter.use(health);

Now Vigilmon can monitor both the shallow endpoint (process alive) and the deep endpoint (database alive) independently, so you know exactly which layer failed when an alert fires.


Step 3: Set up HTTP monitoring in Vigilmon

With your health endpoints exposed, add them to Vigilmon:

  1. Log in to vigilmon.online and go to Monitors → New Monitor
  2. Choose HTTP / HTTPS as the type
  3. Set the URL to https://backstage.yourcompany.com/healthcheck
  4. Set the check interval to 1 minute
  5. Under Expected response, configure:
    • Status code: 200
    • Response body contains: "status":"ok"
  6. Save the monitor

Repeat for the deep health endpoint:

  1. Create a second monitor for https://backstage.yourcompany.com/health/deep
  2. Set expected status: 200
  3. Set response body: "database":"connected"

If the shallow check passes but the deep check fails, you know Backstage is responding but the database is down — a much faster diagnosis than tailing logs.


Step 4: Monitor PostgreSQL via TCP

Backstage's PostgreSQL database can fail independently. Add a TCP port monitor to catch database-level outages:

  1. In Vigilmon, go to Monitors → New Monitor
  2. Choose TCP Port as the type
  3. Enter your database host and port: db.yourcompany.com / 5432
  4. Save the monitor

If your PostgreSQL is running in a private network (recommended), you can monitor it via the same host that Backstage runs on by adding a lightweight TCP proxy or exposing a dedicated monitoring port. Alternatively, monitor the deep health endpoint — when PostgreSQL is unreachable, it will return HTTP 503.

For Kubernetes deployments, monitor the Service ClusterIP via a TCP check from within the cluster or expose the port through your ingress:

# kubernetes/postgres-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: backstage-postgres
  namespace: backstage
spec:
  selector:
    app: postgres
  ports:
    - protocol: TCP
      port: 5432
      targetPort: 5432
  type: ClusterIP

Step 5: Configure alerts and a status page

Set up alert channels so your team is notified immediately when Backstage goes down:

Webhook alert (Slack or Discord):

  1. Go to Alert Channels → Add Channel → Webhook
  2. Paste your Slack webhook URL
  3. Assign the channel to all Backstage monitors

Example Slack alert payload Vigilmon sends on an incident:

{
  "monitor_name": "Backstage /healthcheck",
  "status": "down",
  "url": "https://backstage.yourcompany.com/healthcheck",
  "started_at": "2024-03-15T09:12:00Z",
  "duration_seconds": 120
}

Status page for your engineering team:

  1. Go to Status Pages → New Status Page
  2. Name it "Developer Portal Status"
  3. Add your Backstage monitors (shallow health, deep health, PostgreSQL TCP)
  4. Publish the page

Share the status page URL in your internal engineering handbook and Slack channel so engineers can check portal status without paging on-call. The page automatically shows uptime history and active incidents.


Putting it all together: Docker Compose example

Here's a complete docker-compose.yml for a production Backstage deployment with health checks that align with Vigilmon's monitoring:

version: "3.9"

services:
  backstage:
    image: your-registry/backstage:latest
    build:
      context: .
      dockerfile: packages/backend/Dockerfile
    ports:
      - "7007:7007"
    environment:
      POSTGRES_HOST: db
      POSTGRES_PORT: 5432
      POSTGRES_USER: backstage
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      GITHUB_TOKEN: ${GITHUB_TOKEN}
      AUTH_GITHUB_CLIENT_ID: ${AUTH_GITHUB_CLIENT_ID}
      AUTH_GITHUB_CLIENT_SECRET: ${AUTH_GITHUB_CLIENT_SECRET}
    depends_on:
      db:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:7007/healthcheck"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 30s
    restart: unless-stopped

  db:
    image: postgres:15-alpine
    environment:
      POSTGRES_DB: backstage_plugin_catalog
      POSTGRES_USER: backstage
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U backstage"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped

  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/conf.d/default.conf
      - /etc/letsencrypt:/etc/letsencrypt:ro
    depends_on:
      backstage:
        condition: service_healthy
    restart: unless-stopped

volumes:
  postgres_data:

Monitoring checklist

| What to monitor | Type | URL / Host | Alert on | |---|---|---|---| | Backstage app | HTTP | /healthcheck | Non-200 or timeout | | Backstage + DB | HTTP | /health/deep | Non-200 or database≠connected | | PostgreSQL | TCP | db-host:5432 | Connection refused | | Nginx / TLS | HTTP | / | Non-200 or cert expiry |


Summary

Backstage is a critical piece of developer infrastructure — when it's down, engineers lose their catalog, docs, and scaffolding tools. A three-layer monitoring setup (shallow HTTP health, deep database health, and TCP port check for PostgreSQL) gives you precise failure attribution in seconds. With Vigilmon's free tier you can add all three monitors, configure Slack alerts, and publish a status page in under five minutes.

Start monitoring your developer portal for free at vigilmon.online.

Monitor your app with Vigilmon

Free plan — 5 monitors, no credit card required. Up and running in 60 seconds.

Start free →