Grav is a modern open-source flat-file CMS built in PHP. It requires no database — all content lives in YAML and Markdown files rendered via Twig templating — making it fast, portable, and easy to deploy. Grav's flat-file architecture means there are fewer moving parts than a WordPress or Drupal installation, but it also means failures are more opaque: when PHP or the web server breaks, the entire site returns a blank page or 500 error with no log aggregator or database health panel to check. The Grav Admin plugin at /admin/dashboard — the primary interface for non-developer content editors — can fail independently of the public site, leaving editors locked out while the frontend appears healthy. When Grav's built-in task scheduler stops running (cache clears, backup jobs), stale content and missing backups accumulate silently. Vigilmon gives you external monitoring for Grav's web UI, Admin plugin, PHP runtime, scheduled tasks, and SSL certificates so you catch failures before they affect editors or site visitors.
What You'll Set Up
- Grav homepage availability via HTTP keyword check
- Grav Admin plugin availability (
/admin/dashboard) - PHP/web server runtime health check
- Grav scheduled task health via cron heartbeats (cache clear, backup generation)
- SSL certificate expiry alerts for HTTPS deployments
Prerequisites
- Grav 1.7+ running on Apache or Nginx with PHP 8.1+
- Grav Admin plugin installed
- Shell access to the Grav host
- A free Vigilmon account
Step 1: Monitor the Grav Homepage
The Grav homepage is your first indicator of overall site health. Grav renders pages dynamically from flat files on each request (or from cache) — when PHP-FPM crashes, the web server loses its PHP socket, or a Grav plugin throws an uncaught exception, the homepage returns a blank page or 500 error. Monitor public availability with an HTTP keyword check:
- In Vigilmon, click Add Monitor → HTTP Keyword.
- Set the URL to
https://your-grav-domain.com. - Set Keyword to your site's title or a string that appears in every page (e.g., your brand name or a nav link text).
- Set Check interval to
2 minutes.
For a more reliable check that doesn't depend on a specific page title, use an HTTP status check:
- In Vigilmon, click Add Monitor → HTTP.
- Set the URL to
https://your-grav-domain.com. - Set Expected status code to
200. - Set Check interval to
2 minutes.
Vigilmon checks the homepage every 2 minutes from an external server — it catches failures that local process monitors miss, like an Nginx misconfiguration that passes pgrep nginx checks but returns 500 to real clients.
Step 2: Monitor the Grav Admin Plugin
The Grav Admin plugin provides the web-based content editor interface at /admin/dashboard. It can fail independently of the public site — a plugin conflict, a broken Grav Admin update, or a missing session directory can break the Admin UI while the public frontend continues serving cached content. Editors who cannot reach /admin/dashboard cannot publish or update content:
- In Vigilmon, click Add Monitor → HTTP Keyword.
- Set the URL to
https://your-grav-domain.com/admin. - Set Keyword to
GravorDashboard(text present on the Admin login page). - Set Check interval to
5 minutes.
For Admin instances behind a VPN or IP restriction, monitor from the Grav host itself:
#!/bin/bash
# /usr/local/bin/grav-admin-check.sh
HEARTBEAT_URL="https://vigilmon.online/heartbeat/abc123"
GRAV_URL="https://your-grav-domain.com/admin"
EXPECTED_KEYWORD="Grav"
TIMEOUT=15
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" --max-time "$TIMEOUT" -L "$GRAV_URL")
HTTP_RESPONSE=$(curl -s --max-time "$TIMEOUT" -L "$GRAV_URL")
if [ "$HTTP_CODE" != "200" ]; then
echo "Admin UI returned HTTP $HTTP_CODE"
exit 1
fi
if echo "$HTTP_RESPONSE" | grep -qi "$EXPECTED_KEYWORD"; then
echo "Admin UI OK: keyword found"
curl -s "$HEARTBEAT_URL"
else
echo "Admin UI keyword '$EXPECTED_KEYWORD' missing — possible plugin failure"
exit 1
fi
Install as a cron heartbeat at 5-minute intervals:
chmod +x /usr/local/bin/grav-admin-check.sh
(crontab -l 2>/dev/null; echo "*/5 * * * * /usr/local/bin/grav-admin-check.sh >> /var/log/grav-admin-health.log 2>&1") | crontab -
Step 3: Monitor PHP and Web Server Health
Grav's flat-file architecture means PHP is doing all the work on every uncached request. PHP-FPM pool exhaustion, memory limit errors (Fatal error: Allowed memory size exhausted), or web server misconfigurations that prevent PHP from executing are common failure modes that don't appear in process monitors. Add a lightweight PHP health endpoint to distinguish PHP failures from web server failures:
Create a health check file:
<?php
// user/pages/health/default.md — or place in webroot directly
// Option A: Simple PHP echo (place in webroot as health.php)
$phpVersion = PHP_VERSION;
$memoryLimit = ini_get('memory_limit');
$memUsage = round(memory_get_usage(true) / 1024 / 1024, 2);
http_response_code(200);
header('Content-Type: application/json');
echo json_encode([
'status' => 'ok',
'php_version' => $phpVersion,
'memory_limit' => $memoryLimit,
'memory_used_mb' => $memUsage,
]);
For Grav's routing system, add a simple page instead:
# user/pages/health/default.md
---
title: Health
routable: false
visible: false
---
OK
Then configure Vigilmon to check it:
- In Vigilmon, click Add Monitor → HTTP Keyword.
- Set URL to
https://your-grav-domain.com/health(or/health.php). - Set Keyword to
OKorok. - Set Check interval to
2 minutes.
Alternatively, check PHP-FPM pool status from the shell:
#!/bin/bash
# /usr/local/bin/grav-php-check.sh
HEARTBEAT_URL="https://vigilmon.online/heartbeat/def456"
PHP_FPM_POOL_STATUS="/var/run/php/php8.1-fpm.sock" # adjust PHP version
# Check PHP-FPM is responding
if php -r "echo 'ok';" > /dev/null 2>&1; then
echo "PHP CLI OK"
# Also check FPM socket exists and is accepting connections
if [ -S "$PHP_FPM_POOL_STATUS" ] || pgrep -x "php-fpm8.1" > /dev/null 2>/dev/null || pgrep -x "php-fpm" > /dev/null 2>/dev/null; then
echo "PHP-FPM process running"
curl -s "$HEARTBEAT_URL"
else
echo "PHP-FPM process not found"
exit 1
fi
else
echo "PHP CLI not working"
exit 1
fi
Step 4: Monitor Grav Scheduled Tasks
Grav's built-in task scheduler (introduced in Grav 1.6) handles cache clearing, backup generation, and custom maintenance tasks configured via the Admin plugin. When the cron job that triggers Grav's scheduler stops running — due to a crontab misconfiguration, a root cron being wiped during a server migration, or a PHP timeout — Grav accumulates stale cache and misses scheduled backups.
Monitor the scheduler execution with Vigilmon heartbeats:
Cache clearing task:
#!/bin/bash
# /usr/local/bin/grav-cache-clear.sh
HEARTBEAT_URL="https://vigilmon.online/heartbeat/ghi789"
GRAV_ROOT="/var/www/grav"
# Run Grav cache clear and ping on success
if php "$GRAV_ROOT/bin/grav" cache --all --env=prod >> /var/log/grav-cache-clear.log 2>&1; then
echo "Cache cleared successfully"
curl -s "$HEARTBEAT_URL"
else
echo "Cache clear failed"
exit 1
fi
Backup task:
#!/bin/bash
# /usr/local/bin/grav-backup.sh
HEARTBEAT_URL="https://vigilmon.online/heartbeat/jkl012"
GRAV_ROOT="/var/www/grav"
MAX_BACKUP_AGE_HOURS=26 # Alert if newest backup is older than 26 hours
# Run Grav backup
if php "$GRAV_ROOT/bin/plugin" backup backup >> /var/log/grav-backup.log 2>&1; then
echo "Backup completed"
curl -s "$HEARTBEAT_URL"
else
# Check if a recent backup exists even if the command failed
NEWEST_BACKUP=$(find "$GRAV_ROOT/backup" -name "*.zip" -newer /tmp/grav-backup-sentinel 2>/dev/null | head -1)
if [ -n "$NEWEST_BACKUP" ]; then
echo "Backup found despite command exit: $NEWEST_BACKUP"
curl -s "$HEARTBEAT_URL"
else
echo "Backup failed and no recent backup found"
exit 1
fi
fi
Grav built-in scheduler trigger (for Grav 1.6+):
#!/bin/bash
# /usr/local/bin/grav-scheduler.sh
HEARTBEAT_URL="https://vigilmon.online/heartbeat/mno345"
GRAV_ROOT="/var/www/grav"
if php "$GRAV_ROOT/bin/grav" scheduler 1>> /dev/null 2>&1; then
echo "Grav scheduler executed successfully"
curl -s "$HEARTBEAT_URL"
else
echo "Grav scheduler execution failed"
exit 1
fi
Add the cron entries:
chmod +x /usr/local/bin/grav-cache-clear.sh /usr/local/bin/grav-backup.sh /usr/local/bin/grav-scheduler.sh
# Cache clear: every 6 hours
(crontab -l 2>/dev/null; echo "0 */6 * * * /usr/local/bin/grav-cache-clear.sh") | crontab -
# Backup: daily at 2 AM
(crontab -l 2>/dev/null; echo "0 2 * * * /usr/local/bin/grav-backup.sh") | crontab -
# Grav scheduler: every minute (Grav handles its own frequency logic)
(crontab -l 2>/dev/null; echo "* * * * * /usr/local/bin/grav-scheduler.sh") | crontab -
Set Vigilmon heartbeat expected intervals: 7 hours for cache clear, 25 hours for backup, and 2 minutes for the scheduler trigger.
Step 5: Monitor SSL Certificates
Grav sites frequently run on shared hosting or simple VPS deployments where Let's Encrypt auto-renewal is configured manually. An expired Let's Encrypt certificate causes browsers to block the site entirely — visitors see a hard security warning, not a degraded experience. Monitor certificate expiry before it impacts your site:
- In Vigilmon, click Add Monitor → SSL Certificate.
- Set Domain to
your-grav-domain.com. - Set Alert before expiry to
14 days.
For multi-site Grav setups with multiple domains, add an SSL monitor for each domain. The Grav CMS can host multiple sites with different domains via its multi-site feature — each domain has its own certificate renewal timeline.
Check certificate expiry from the shell (useful for monitoring from the host):
#!/bin/bash
# /usr/local/bin/grav-ssl-check.sh
HEARTBEAT_URL="https://vigilmon.online/heartbeat/pqr678"
DOMAIN="your-grav-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"
exit 1
fi
Run daily with a 25 hour Vigilmon heartbeat.
Step 6: Set Up Alert Channels
Configure Vigilmon to send Grav alerts to the right people:
- Go to Alert Channels in Vigilmon.
- Add email notification to the site owner or DevOps contact for all monitors — Grav sites often have small teams without dedicated ops staff.
- Add a Slack webhook to
#site-opsif your team uses Slack. - Set Consecutive failures before alert to
2for the homepage monitor — brief PHP-FPM restarts during server maintenance cause transient failures that resolve within a minute. - Set Consecutive failures before alert to
1for the Admin plugin monitor — editor access failures need immediate attention.
Add a maintenance window during Grav updates:
curl -X POST https://vigilmon.online/api/maintenance \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{
"monitor_id": "GRAV_HOMEPAGE_MONITOR_ID",
"duration_minutes": 15,
"reason": "Grav CMS update"
}'
# Then update Grav
cd /var/www/grav && php bin/gpm selfupgrade && php bin/gpm update
Summary
| Monitor | Target | What It Catches |
|---|---|---|
| HTTP keyword (homepage) | https://your-grav-domain.com | PHP crash, web server failure, plugin error |
| HTTP keyword (Admin) | /admin | Admin plugin failure, session issue |
| HTTP keyword (PHP health) | /health.php | PHP-FPM failure, memory exhaustion |
| Cron heartbeat (cache clear) | grav cache --all | Scheduler breakdown, stale cache |
| Cron heartbeat (backup) | plugin backup backup | Missing backups, backup job failure |
| Cron heartbeat (scheduler) | grav scheduler | Scheduler execution gaps |
| SSL certificate | your-grav-domain.com | Certificate expiry, Let's Encrypt renewal failure |
Grav's flat-file architecture makes it lightweight and portable, but it also means failures produce cryptic web server errors rather than informative application logs. External monitoring with Vigilmon catches the full failure surface — site outages, Admin plugin failures, PHP runtime issues, and scheduler gaps — before editors or site visitors notice anything is wrong.
Start monitoring for free at vigilmon.online.