Keystone is a powerful open-source Node.js and TypeScript headless CMS and application framework. It is GraphQL-native, uses Prisma ORM for database access, and includes an auto-generated Admin UI built on Next.js — making it a popular choice for building custom content APIs, application backends, and editorial interfaces. When Keystone's GraphQL API goes down, every API consumer — whether a Next.js frontend, a mobile app, or an automated content pipeline — immediately begins failing. When the Admin UI stops responding, content editors cannot create or update content. When the Prisma database connection drops, Keystone's entire request handling fails. When scheduled task hooks stop firing, automated content processing and timed publications never execute. Vigilmon gives you external monitoring for Keystone's GraphQL API, Admin UI, database connectivity, and scheduled task execution so you catch failures before they surface as API consumer errors or missed content publications.
What You'll Set Up
- Keystone GraphQL API availability via introspection query
- Admin UI availability (Next.js-based
/_next/staticcheck) - Database connectivity via API response verification
- Scheduled task and automated content processing health via cron heartbeats
- SSL certificate expiry alerts for HTTPS Keystone deployments
Prerequisites
- Keystone 6.x running in production (
NODE_ENV=production) - PostgreSQL or SQLite database accessible from the Keystone host
- Shell access to the Keystone host
- A free Vigilmon account
Step 1: Monitor the Keystone GraphQL API
The Keystone GraphQL API at /api/graphql is the single entry point for all data reads and writes — content fetches, mutations, and authentication all go through GraphQL. An introspection query is the lightest possible health check: it asks GraphQL to describe its own schema, which requires the entire Keystone stack (Node.js process, GraphQL layer, Prisma, and database) to be functioning:
- In Vigilmon, click Add Monitor → HTTP Keyword.
- Set the URL to
https://your-keystone-domain.com/api/graphql. - Set Method to
POST. - Set Request body to
{"query": "{ __typename }"}. - Set Request header
Content-Typetoapplication/json. - Set Keyword to
data(present in a valid GraphQL response). - Set Check interval to
2 minutes.
For a shell-based check (internal or private Keystone instances):
#!/bin/bash
# /usr/local/bin/keystone-graphql-check.sh
HEARTBEAT_URL="https://vigilmon.online/heartbeat/abc123"
KEYSTONE_URL="https://your-keystone-domain.com"
TIMEOUT=15
GQL_RESPONSE=$(curl -s \
--max-time "$TIMEOUT" \
-X POST \
-H "Content-Type: application/json" \
-d '{"query": "{ __typename }"}' \
"${KEYSTONE_URL}/api/graphql")
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" \
--max-time "$TIMEOUT" \
-X POST \
-H "Content-Type: application/json" \
-d '{"query": "{ __typename }"}' \
"${KEYSTONE_URL}/api/graphql")
if [ "$HTTP_CODE" = "200" ] && echo "$GQL_RESPONSE" | grep -q '"data"'; then
echo "GraphQL API OK: HTTP 200, data field present"
curl -s "$HEARTBEAT_URL"
else
echo "GraphQL API FAILED: HTTP $HTTP_CODE, response: $GQL_RESPONSE"
exit 1
fi
Install as a cron heartbeat:
chmod +x /usr/local/bin/keystone-graphql-check.sh
(crontab -l 2>/dev/null; echo "*/2 * * * * /usr/local/bin/keystone-graphql-check.sh >> /var/log/keystone-graphql-health.log 2>&1") | crontab -
A failed GraphQL check means all API consumers are broken — set the Vigilmon heartbeat to 3 minutes and alert immediately on the first failure.
Step 2: Monitor the Keystone Admin UI
The Keystone Admin UI is a Next.js application served by the Keystone process alongside the GraphQL API. It can fail independently of the API — a broken Next.js build artifact, a missing static file, or a React hydration error can leave the Admin UI inaccessible while the GraphQL API continues responding. Check for Next.js static asset availability as a proxy for Admin UI health:
- In Vigilmon, click Add Monitor → HTTP.
- Set the URL to
https://your-keystone-domain.com/api/graphql(the Admin UI root page). - Set Expected status to
200. - Set Check interval to
5 minutes.
For a more targeted Admin UI check using the Next.js static manifest:
#!/bin/bash
# /usr/local/bin/keystone-admin-check.sh
HEARTBEAT_URL="https://vigilmon.online/heartbeat/def456"
KEYSTONE_URL="https://your-keystone-domain.com"
TIMEOUT=15
# Check the Admin UI root page loads
ADMIN_HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" \
--max-time "$TIMEOUT" \
"${KEYSTONE_URL}")
# Check Next.js static assets are available (confirms build artifacts are intact)
STATIC_HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" \
--max-time "$TIMEOUT" \
"${KEYSTONE_URL}/_next/static/chunks/main.js" 2>/dev/null || echo 000)
# Primary check: Admin UI root returns 200
if [ "$ADMIN_HTTP_CODE" = "200" ]; then
echo "Admin UI OK: HTTP 200"
curl -s "$HEARTBEAT_URL"
elif [ "$ADMIN_HTTP_CODE" = "302" ] || [ "$ADMIN_HTTP_CODE" = "301" ]; then
# Redirect to /signin is normal when not authenticated
echo "Admin UI OK: redirect to sign-in (HTTP $ADMIN_HTTP_CODE)"
curl -s "$HEARTBEAT_URL"
else
echo "Admin UI FAILED: HTTP $ADMIN_HTTP_CODE (static: $STATIC_HTTP_CODE)"
exit 1
fi
chmod +x /usr/local/bin/keystone-admin-check.sh
(crontab -l 2>/dev/null; echo "*/5 * * * * /usr/local/bin/keystone-admin-check.sh >> /var/log/keystone-admin-health.log 2>&1") | crontab -
Set the Vigilmon heartbeat to 7 minutes. Admin UI failures that persist more than two check cycles indicate a broken Next.js build or a failed Keystone restart after a code deployment.
Step 3: Monitor Database Connectivity via API Response
Keystone uses Prisma as its ORM and requires a live database connection for all GraphQL operations. Unlike a simple database ping, the best way to verify Keystone's database connectivity is via the GraphQL API itself — if the API responds successfully to a data query, Prisma's connection pool is healthy. A failed data query (while the introspection query in Step 1 succeeds) indicates a partial database issue:
#!/bin/bash
# /usr/local/bin/keystone-db-check.sh
HEARTBEAT_URL="https://vigilmon.online/heartbeat/ghi789"
KEYSTONE_URL="https://your-keystone-domain.com"
TIMEOUT=15
# Replace 'User' with a list type that exists in your Keystone schema
# This query fetches 1 record — fast and tests Prisma + DB together
DB_RESPONSE=$(curl -s \
--max-time "$TIMEOUT" \
-X POST \
-H "Content-Type: application/json" \
-d '{"query": "{ users(take: 1) { id } }"}' \
"${KEYSTONE_URL}/api/graphql")
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" \
--max-time "$TIMEOUT" \
-X POST \
-H "Content-Type: application/json" \
-d '{"query": "{ users(take: 1) { id } }"}' \
"${KEYSTONE_URL}/api/graphql")
if [ "$HTTP_CODE" = "200" ] && echo "$DB_RESPONSE" | grep -q '"data"'; then
# Check for GraphQL errors that indicate database issues
if echo "$DB_RESPONSE" | grep -q '"errors"'; then
ERROR_MSG=$(echo "$DB_RESPONSE" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('errors',[{}])[0].get('message','unknown'))" 2>/dev/null)
echo "GraphQL data query returned errors: $ERROR_MSG"
exit 1
fi
echo "Database connectivity OK via Prisma"
curl -s "$HEARTBEAT_URL"
else
echo "Database connectivity check FAILED: HTTP $HTTP_CODE"
echo "Response: $DB_RESPONSE"
exit 1
fi
For public Keystone instances where the users query requires authentication, use a publicly accessible list type instead:
# Example: query a public content type
{ posts(take: 1) { id } }
Or query the Keystone system health endpoint if available in your Keystone version.
Install the check:
chmod +x /usr/local/bin/keystone-db-check.sh
(crontab -l 2>/dev/null; echo "*/3 * * * * /usr/local/bin/keystone-db-check.sh >> /var/log/keystone-db-health.log 2>&1") | crontab -
Set the Vigilmon heartbeat to 5 minutes. A failing database check combined with a passing introspection check (from Step 1) means the GraphQL layer is up but Prisma has lost its connection — restart the Keystone process to force a Prisma connection pool reset.
Step 4: Monitor Keystone Scheduled Tasks and Hooks
Keystone supports scheduled content operations via Node.js cron libraries (node-cron, cron, node-schedule) integrated into the Keystone configuration, as well as automated content processing through Keystone hooks (beforeOperation, afterOperation, resolveInput). When the Keystone process restarts without the scheduled task configuration re-registering, or when hook logic fails silently, automated workflows (scheduled content publish, content processing pipelines, notification triggers) break without any alert.
Monitor the Node.js process health to ensure hooks can fire:
#!/bin/bash
# /usr/local/bin/keystone-process-check.sh
HEARTBEAT_URL="https://vigilmon.online/heartbeat/jkl012"
KEYSTONE_PORT="${KEYSTONE_PORT:-3000}"
# Check the Keystone process is running and binding to its port
if pgrep -f "keystone start" > /dev/null || \
pgrep -f "node.*keystone" > /dev/null || \
ss -tlnp "sport = :${KEYSTONE_PORT}" 2>/dev/null | grep -q LISTEN; then
echo "Keystone process running on port $KEYSTONE_PORT"
curl -s "$HEARTBEAT_URL"
else
echo "Keystone process not found on port $KEYSTONE_PORT"
exit 1
fi
For scheduled tasks implemented with node-cron inside Keystone, add a heartbeat ping inside the task itself:
// keystone.ts — example scheduled task with Vigilmon heartbeat
import cron from 'node-cron'
export default config({
// ... your Keystone config
extendGraphqlSchema: graphql.extend(base => ({
// ... your extensions
})),
// Run after Keystone connects
// Use an onConnect extension point or wrap in your server startup
})
// Scheduled content publication — runs every 5 minutes
cron.schedule('*/5 * * * *', async () => {
try {
const context = await getContext()
// Find content items scheduled to publish now
const now = new Date()
const toPublish = await context.db.Post.findMany({
where: {
publishAt: { lte: now },
status: { equals: 'scheduled' },
},
})
for (const post of toPublish) {
await context.db.Post.updateOne({
where: { id: post.id },
data: { status: 'published', publishAt: null },
})
}
// Ping Vigilmon heartbeat to confirm the job ran
await fetch('https://vigilmon.online/heartbeat/mno345').catch(() => {})
console.log(`Scheduled publish: ${toPublish.length} items published`)
} catch (err) {
console.error('Scheduled publish failed:', err)
// Do NOT ping heartbeat — Vigilmon will alert on missing heartbeat
}
})
For automated content processing hooks, wrap the hook in a heartbeat ping:
// Schema with hook monitoring
export const Post = list({
fields: {
title: text({ validation: { isRequired: true } }),
content: text(),
status: select({
options: ['draft', 'scheduled', 'published'],
defaultValue: 'draft',
}),
},
hooks: {
afterOperation: {
create: async ({ item }) => {
// Process new content (e.g., generate slug, send notifications)
try {
await processNewContent(item)
// Ping heartbeat to confirm hook pipeline is running
await fetch('https://vigilmon.online/heartbeat/pqr678').catch(() => {})
} catch (err) {
console.error('afterOperation hook failed:', err)
}
},
},
},
})
Set Vigilmon heartbeat expected intervals to match your cron schedule:
- Process health check:
3 minutes - Scheduled publish job:
7 minutes(5-minute cron with 2-minute buffer) - Content processing hook: Depends on your content creation frequency — set to
2xexpected creation interval
Step 5: Monitor SSL Certificates
Keystone runs on Node.js, typically behind an Nginx or Caddy reverse proxy that handles TLS termination. An expired SSL certificate causes all API consumers — Next.js frontends, mobile apps, and webhook listeners — to fail with TLS handshake errors. Monitor certificate expiry before renewal deadlines:
- In Vigilmon, click Add Monitor → SSL Certificate.
- Set Domain to
your-keystone-domain.com. - Set Alert before expiry to
14 days.
For shell-based SSL monitoring:
#!/bin/bash
# /usr/local/bin/keystone-ssl-check.sh
HEARTBEAT_URL="https://vigilmon.online/heartbeat/stu901"
DOMAIN="your-keystone-domain.com"
MIN_DAYS_REMAINING=14
EXPIRY_DATE=$(echo | openssl s_client -servername "$DOMAIN" \
-connect "${DOMAIN}:443" 2>/dev/null | \
openssl x509 -noout -enddate 2>/dev/null | \
cut -d= -f2)
if [ -z "$EXPIRY_DATE" ]; then
echo "Could not retrieve SSL certificate for $DOMAIN"
exit 1
fi
EXPIRY_EPOCH=$(date -d "$EXPIRY_DATE" +%s 2>/dev/null || \
date -j -f "%b %d %T %Y %Z" "$EXPIRY_DATE" +%s 2>/dev/null)
NOW_EPOCH=$(date +%s)
DAYS_REMAINING=$(( (EXPIRY_EPOCH - NOW_EPOCH) / 86400 ))
if [ "$DAYS_REMAINING" -ge "$MIN_DAYS_REMAINING" ]; then
echo "SSL OK: $DAYS_REMAINING days until expiry"
curl -s "$HEARTBEAT_URL"
else
echo "SSL EXPIRING SOON: $DAYS_REMAINING days remaining (threshold: $MIN_DAYS_REMAINING)"
exit 1
fi
Run daily with a 25 hour Vigilmon heartbeat interval.
Step 6: Set Up Alert Channels
Configure Vigilmon to route Keystone alerts where your team will respond:
- Go to Alert Channels in Vigilmon.
- Add a Slack webhook to
#api-alertsfor the GraphQL API monitor — GraphQL failures immediately impact all API consumers. - Add PagerDuty for the database connectivity monitor — a lost Prisma connection takes down the entire Keystone stack.
- Add email notification for the Admin UI and scheduled task monitors — these are important but rarely require on-call response.
- Set Consecutive failures before alert to
1for the GraphQL API monitor — a single failure means all data fetches are breaking. - Set Consecutive failures before alert to
2for the Admin UI monitor — Keystone process restarts during deployments cause brief Admin UI unavailability.
Add maintenance windows during Keystone version upgrades or Prisma migration runs:
# Pause monitors during schema migration
curl -X POST https://vigilmon.online/api/maintenance \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{
"monitor_id": "KEYSTONE_GRAPHQL_MONITOR_ID",
"duration_minutes": 15,
"reason": "Keystone schema migration"
}'
# Run Prisma migration
npx keystone migrate deploy
# Resume monitoring automatically after the maintenance window
Summary
| Monitor | Target | What It Catches |
|---|---|---|
| HTTP keyword (GraphQL API) | /api/graphql introspection | Node.js crash, GraphQL layer failure, process exit |
| Cron heartbeat (Admin UI) | Admin root page | Next.js build failure, Admin UI render error |
| Cron heartbeat (database) | GraphQL data query | Prisma connection loss, database crash |
| Cron heartbeat (process) | Port check / pgrep | Keystone process death, port binding failure |
| In-app heartbeat (scheduled tasks) | node-cron job ping | Scheduler not re-registered after restart |
| In-app heartbeat (hooks) | afterOperation ping | Hook pipeline silence, processing gap |
| SSL certificate | your-keystone-domain.com | Certificate expiry, Nginx TLS config issue |
Keystone's single-process architecture means a crash takes down both the GraphQL API and the Admin UI simultaneously — but database connection loss and hook failures can occur independently, leaving some functionality intact while others fail silently. Vigilmon's combination of external HTTP checks and in-process heartbeat pings gives you full-stack visibility: you know within minutes whether the problem is a process crash, a database issue, or a silent scheduled task failure.
Start monitoring for free at vigilmon.online.