tutorial

Monitoring Your Enclosed Instance with Vigilmon

Keep your self-hosted encrypted note-sharing service healthy with uptime checks, API endpoint monitoring, TLS certificate alerts, and heartbeat monitoring for the expiry cleanup job.

Monitoring Your Enclosed Instance with Vigilmon

Enclosed is a self-hosted, end-to-end encrypted note-sharing service — a private alternative to Pastebin built for sensitive data. Notes are encrypted client-side before they ever reach the server, they expire automatically, and no account is required.

That privacy model creates strict monitoring requirements. If the web server goes down, users can't share notes. If the JavaScript encryption assets aren't served correctly, encryption silently breaks — notes appear to work but aren't secured. If the cleanup job stops running, expired notes linger in storage, violating the privacy contract you promised your users.

This tutorial sets up production monitoring for Enclosed using Vigilmon:

  • Web server and API availability
  • Encryption asset serving verification
  • Note creation and retrieval endpoint health
  • TLS certificate expiry monitoring
  • Heartbeat monitoring for the expiry cleanup job
  • Rate limiting and storage health
  • Slack alerting

Step 1: Add a health check endpoint

Enclosed (Node.js / Nuxt.js, port 8787) doesn't ship a /health endpoint by default. Add a lightweight one using a Nitro server route.

Create server/routes/health.get.ts:

// server/routes/health.get.ts
import { defineEventHandler } from 'h3'
import Database from 'better-sqlite3'
import { readFileSync, existsSync } from 'fs'
import path from 'path'

export default defineEventHandler(async (event) => {
  const checks: Record<string, string> = {}

  // Database connectivity
  try {
    const db = new Database(process.env.SQLITE_DB_PATH ?? './data/enclosed.db')
    db.prepare('SELECT 1').get()
    db.close()
    checks.database = 'ok'
  } catch {
    checks.database = 'error'
  }

  // Verify static JS assets are on disk (encryption depends on them)
  const publicDir = path.resolve('.output/public')
  checks.static_assets = existsSync(publicDir) ? 'ok' : 'error'

  const allOk = Object.values(checks).every((v) => v === 'ok')

  return new Response(
    JSON.stringify({ status: allOk ? 'ok' : 'degraded', checks }),
    {
      status: allOk ? 200 : 503,
      headers: { 'Content-Type': 'application/json' },
    }
  )
})

Test it locally:

curl http://localhost:8787/health
# {"status":"ok","checks":{"database":"ok","static_assets":"ok"}}

A 503 response body tells you which subsystem failed.


Step 2: Monitor web server availability

Point Vigilmon at your health endpoint:

  1. Sign up at vigilmon.online
  2. Click New Monitor → HTTP
  3. URL: https://notes.yourdomain.com/health
  4. Check interval: 1 minute
  5. Expected status: 200
  6. Save

This catches the most common Enclosed failure: the Node.js process exits or crashes, making the service completely unavailable.


Step 3: Verify encryption assets are serving correctly

Enclosed's security model depends entirely on JavaScript running in the browser — if the JS bundle isn't served, encryption doesn't happen. Add a dedicated monitor for the main JS asset.

In Vigilmon, create a second HTTP monitor:

  • URL: https://notes.yourdomain.com/_nuxt/ (adjust to your actual bundle path — check the page source for the <script src> filename)
  • Expected status: 200
  • Keyword check: add a body keyword that should appear in the JS (e.g. encrypt or a known function name from the source)

Alternatively, monitor the root page and check for the presence of a <script> tag:

  • URL: https://notes.yourdomain.com/
  • Expected status: 200
  • Keyword check: _nuxt

If the static asset pipeline breaks (e.g. a bad deploy wipes the .output/public directory), this monitor fails immediately — before any users discover their notes are being sent unencrypted.


Step 4: Monitor the note creation API endpoint

The core API — creating a new note — should be fast and reliable. Monitor it with Vigilmon's HTTP monitor in keyword mode rather than actually creating a test note (to avoid polluting storage):

  • URL: https://notes.yourdomain.com/api/notes
  • Method: HEAD or OPTIONS
  • Expected status: 405 (Method Not Allowed) or 200 — whichever your Enclosed version returns for a non-POST to this endpoint

If you want a real functional probe, write a small script that POSTs a test note and checks the response:

#!/bin/bash
# probe-note-creation.sh
RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" \
  -X POST https://notes.yourdomain.com/api/notes \
  -H "Content-Type: application/json" \
  -d '{"content":"dGVzdA==","ttl":60}')

if [ "$RESPONSE" = "201" ]; then
  # Ping Vigilmon heartbeat to signal the probe succeeded
  curl -s "$VIGILMON_NOTE_API_HEARTBEAT_URL" > /dev/null
fi

Schedule this probe as a cron job and use a Vigilmon Heartbeat Monitor (Step 7) to detect when it stops running.


Step 5: Monitor the note retrieval endpoint

Note retrieval is the read path. Monitor a URL that exercises the route handler without requiring a real note ID:

  • URL: https://notes.yourdomain.com/api/notes/probe-id-that-does-not-exist
  • Expected status: 404

A 404 from the API is healthy — it means the server processed the request and searched the database. Any other status (502, 503, timeout) indicates a real problem.


Step 6: TLS certificate expiry monitoring

Enclosed is a security-focused tool. Any TLS issue breaks the security model: browsers show certificate warnings, users can't access the service, and the trust relationship is destroyed.

In Vigilmon:

  1. Go to New Monitor → SSL Certificate
  2. Domain: notes.yourdomain.com
  3. Alert thresholds: 30 days (warning) and 7 days (critical)
  4. Save

This gives you a 30-day runway to renew before expiry and a 7-day hard alert if renewal hasn't happened. If you use Let's Encrypt with auto-renewal, this monitor catches cases where the renewal cron failed silently.


Step 7: Heartbeat monitoring for the note expiry cleanup job

Enclosed deletes expired notes on a schedule. If this job stops running, your storage fills with stale data and — more critically — you're failing to delete notes that users expected to expire. This is a privacy compliance failure.

Add a heartbeat ping to your cleanup job. If you're running Enclosed with a custom scheduler or cron:

#!/bin/bash
# cleanup-expired-notes.sh
# Your existing cleanup logic here
node /app/scripts/cleanup-expired.js

EXIT_CODE=$?
if [ $EXIT_CODE -eq 0 ]; then
  curl -s "$VIGILMON_CLEANUP_HEARTBEAT_URL" > /dev/null
fi

In Vigilmon:

  1. Click New Monitor → Heartbeat
  2. Name: Enclosed Note Expiry Cleanup
  3. Expected interval: match your cron schedule (e.g. 1 hour)
  4. Grace period: 10 minutes
  5. Copy the ping URL → set as VIGILMON_CLEANUP_HEARTBEAT_URL env variable
  6. Save

If Enclosed handles cleanup internally (via a built-in task runner), you can wrap the container and ping the heartbeat URL on successful container health checks:

# docker-compose.yml
services:
  enclosed:
    image: ghcr.io/CorentinTh/enclosed:latest
    ports:
      - "8787:8787"
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8787/health"]
      interval: 60s
      timeout: 10s
      retries: 3
    environment:
      - VIGILMON_CLEANUP_HEARTBEAT_URL=${VIGILMON_CLEANUP_HEARTBEAT_URL}

Step 8: Rate limiting health monitoring

Enclosed includes rate limiting middleware to prevent note spam. Monitor that it's functioning by checking that the API rejects excessive requests appropriately.

Add a Vigilmon HTTP monitor in custom header mode:

  • URL: https://notes.yourdomain.com/api/notes
  • Expected response: includes X-RateLimit-Limit header (indicating rate limiting middleware is active)

You can check this with a Vigilmon keyword monitor on the response headers, or via a synthetic probe script that validates the header is present.


Step 9: Slack alerts

In Vigilmon, go to Notifications → New Channel → Slack and paste your Slack incoming webhook URL.

Enable the Slack channel on all your Enclosed monitors. You'll receive:

🔴 DOWN: notes.yourdomain.com/health
Status: 503 Service Unavailable
Detected from: EU-West, US-East
3 minutes ago

And for the cleanup heartbeat:

🔴 MISSED HEARTBEAT: Enclosed Note Expiry Cleanup
Last ping: 2h 15m ago (expected every 1h)

The heartbeat miss alert is the critical one — silent failures in the cleanup job are the hardest to detect without it.


Step 10: Status page

Create a status page so users know where to check when Enclosed is unavailable:

  1. Go to Status Pages → New Status Page in Vigilmon
  2. Add all your Enclosed monitors
  3. Set the page name (e.g. "Enclosed Notes Status")
  4. Copy the public URL

Link it from your Enclosed instance's error page or README so users know to check it when they can't access the service.


What you've built

| What | How | |------|-----| | Web server health | Vigilmon HTTP monitor → /health | | Encryption asset check | Vigilmon keyword monitor → /_nuxt/ | | Note creation API | Functional probe script + heartbeat | | Note retrieval API | HTTP monitor → /api/notes/:id expecting 404 | | TLS certificate | Vigilmon SSL monitor, 30-day + 7-day alert | | Expiry cleanup job | Heartbeat monitor, 1-hour expected interval | | Rate limiting | Header presence check via HTTP monitor | | Slack alerts | Vigilmon Slack notification channel | | Status page | Vigilmon public status page |

Enclosed's privacy guarantees only hold when every layer is running. Vigilmon ensures you know the moment any layer fails.


Next steps

  • Add a monitor for storage quota (disk usage alert before Enclosed can't accept new notes)
  • Set up Vigilmon's response time history to catch database slowdowns before they affect note creation latency
  • If you run Enclosed behind a reverse proxy (nginx/Caddy), add a separate monitor for the proxy health endpoint
  • Consider a multi-region Vigilmon check if your users are globally distributed

Get started 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 →