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:
- Sign up at vigilmon.online
- Click New Monitor → HTTP
- URL:
https://notes.yourdomain.com/health - Check interval: 1 minute
- Expected status: 200
- 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.
encryptor 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:
- Go to New Monitor → SSL Certificate
- Domain:
notes.yourdomain.com - Alert thresholds: 30 days (warning) and 7 days (critical)
- 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:
- Click New Monitor → Heartbeat
- Name:
Enclosed Note Expiry Cleanup - Expected interval: match your cron schedule (e.g. 1 hour)
- Grace period: 10 minutes
- Copy the ping URL → set as
VIGILMON_CLEANUP_HEARTBEAT_URLenv variable - 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-Limitheader (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:
- Go to Status Pages → New Status Page in Vigilmon
- Add all your Enclosed monitors
- Set the page name (e.g. "Enclosed Notes Status")
- 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.