Nuclei is a fast, template-driven vulnerability scanner used by security teams to assess their attack surface continuously. In production security workflows, Nuclei runs as a scheduled job or a persistent service — scanning targets on a cadence, feeding results into a SIEM, or powering an automated vulnerability management pipeline. When the infrastructure running Nuclei goes down, scheduled scans silently stop. Vulnerabilities accumulate undetected. Your security posture degrades without any signal.
This tutorial covers production-grade uptime monitoring for Nuclei infrastructure using Vigilmon. We will walk through:
- Monitoring the services Nuclei depends on (API wrappers, result storage, scan schedulers)
- Monitoring Nuclei scan health via custom health endpoints
- Alerting on scan pipeline failures
- Webhook alerts for DOWN/UP events
- Docker and Kubernetes deployment monitoring
Prerequisites
- Nuclei integrated into a scanning pipeline (scheduled, API-driven, or containerized)
- A free account at vigilmon.online
Part 1: What to monitor in a Nuclei deployment
Nuclei itself is a CLI tool — it does not expose an HTTP server by default. What you monitor depends on how Nuclei is deployed:
| Deployment pattern | What to monitor | |--------------------|-----------------| | Nuclei API wrapper | The HTTP API exposing scan submission and results | | Scan scheduler (cron + Nuclei) | The scheduler's health endpoint or last-run timestamp | | Nuclei in Docker/K8s | The container/pod status and any sidecar health endpoint | | Nuclei → database pipeline | The database and result ingestion service | | Nuclei + nuclei-templates server | The template update server |
The most common production pattern is a REST API wrapper that accepts scan requests, runs Nuclei, and stores results. That wrapper is what you expose to Vigilmon.
Part 2: Add a health endpoint to your Nuclei API wrapper
If you manage a Nuclei API service, add a /health or /ping endpoint:
// nuclei-api/src/health.ts
import express from 'express';
import { execSync } from 'child_process';
import { checkDatabaseConnection } from './db';
const router = express.Router();
router.get('/health', async (req, res) => {
const checks: Record<string, string> = {};
// Check that the nuclei binary is reachable
try {
const version = execSync('nuclei -version 2>&1', { timeout: 5000 })
.toString()
.trim();
checks.nuclei = version.includes('Nuclei Engine') ? 'ok' : 'degraded';
} catch {
checks.nuclei = 'unavailable';
}
// Check database connectivity
try {
await checkDatabaseConnection();
checks.database = 'ok';
} catch {
checks.database = 'unavailable';
}
// Check template directory exists and is non-empty
try {
const count = execSync('ls ~/.local/nuclei-templates | wc -l', { timeout: 3000 })
.toString()
.trim();
checks.templates = parseInt(count) > 100 ? 'ok' : 'stale';
} catch {
checks.templates = 'unavailable';
}
const allOk = Object.values(checks).every(v => v === 'ok');
const status = allOk ? 'ok' : 'degraded';
res.status(allOk ? 200 : 503).json({ status, checks });
});
export default router;
This endpoint returns:
{
"status": "ok",
"checks": {
"nuclei": "ok",
"database": "ok",
"templates": "ok"
}
}
Or on failure:
{
"status": "degraded",
"checks": {
"nuclei": "ok",
"database": "unavailable",
"templates": "stale"
}
}
Part 3: Monitor scan pipeline liveness with a heartbeat endpoint
For Nuclei deployments that run scheduled scans, add a "last scan" heartbeat endpoint that fails if no scan has completed within the expected window:
# nuclei_api/health.py (Flask)
from flask import Flask, jsonify
from datetime import datetime, timedelta
from models import db, ScanResult
app = Flask(__name__)
@app.route('/health/scan-liveness')
def scan_liveness():
# Fail if no scan has completed in the last 6 hours
threshold = datetime.utcnow() - timedelta(hours=6)
last_scan = ScanResult.query.filter(
ScanResult.completed_at >= threshold
).order_by(ScanResult.completed_at.desc()).first()
if last_scan is None:
return jsonify({
'status': 'stale',
'message': 'No scans completed in the last 6 hours'
}), 503
return jsonify({
'status': 'ok',
'last_scan_at': last_scan.completed_at.isoformat(),
'targets_scanned': last_scan.target_count
}), 200
This is the most valuable monitor for scheduled scan workflows — it detects silent cron failures, scan timeouts, and scheduler crashes that leave the API alive but scans stopped.
Part 4: Set up HTTP monitoring in Vigilmon
Monitor the Nuclei API health
- Log in to vigilmon.online and click Add Monitor.
- Choose HTTP(S) monitor.
- Enter:
https://nuclei-api.example.com/health - Set interval to 1 minute.
- Add a keyword check: must contain
"status":"ok". - Add your alert channel.
- Click Save.
Monitor scan pipeline liveness
- Click Add Monitor → HTTP(S) monitor.
- Enter:
https://nuclei-api.example.com/health/scan-liveness - Set interval to 15 minutes (scans are not instant; polling every minute would be noisy).
- Add a keyword check: must contain
"status":"ok". - Click Save.
Monitor the result database
If your Nuclei results are stored in PostgreSQL, add a database-specific monitor:
- Click Add Monitor → HTTP(S) monitor.
- Enter a database health proxy endpoint (e.g., via PgBouncer or a sidecar health check).
- Set interval to 2 minutes.
- Click Save.
| Monitor | URL | Interval | Check |
|---------|-----|----------|-------|
| Nuclei API | /health | 1 min | keyword: "status":"ok" |
| Scan liveness | /health/scan-liveness | 15 min | keyword: "status":"ok" |
| Result store | /health/db | 2 min | HTTP 200 |
Part 5: SSL certificate monitoring
If your Nuclei API is exposed over HTTPS, add an SSL monitor:
- In Vigilmon, click Add Monitor.
- Choose SSL monitor.
- Enter:
nuclei-api.example.com - Set alert threshold to 14 days before expiry.
- Add your alert channel.
Part 6: Webhook alerts
Configure Vigilmon webhooks to notify your security team when the scan pipeline fails:
// webhook-receiver.ts
import express from 'express';
const app = express();
app.use(express.json());
app.post('/webhook/vigilmon', (req, res) => {
const { monitor_name, status, url, response_code, checked_at } = req.body;
if (status === 'down') {
console.error('[VIGILMON] Nuclei pipeline DOWN', {
monitor: monitor_name,
url,
code: response_code,
at: checked_at,
});
// Alert security team — unscanned assets accumulate risk
notifySecurityTeam({
severity: 'high',
title: `Nuclei scan pipeline offline: ${monitor_name}`,
body: `No vulnerability scans running since ${checked_at}. Assets are unmonitored.`,
runbook: 'https://wiki.example.com/runbooks/nuclei-pipeline',
});
} else if (status === 'up') {
console.info('[VIGILMON] Nuclei pipeline recovered', {
monitor: monitor_name,
});
}
res.sendStatus(204);
});
app.listen(3000);
Vigilmon payload:
{
"monitor_id": "mon_abc123",
"monitor_name": "Nuclei Scan Liveness",
"status": "down",
"url": "https://nuclei-api.example.com/health/scan-liveness",
"checked_at": "2026-06-30T08:01:00Z",
"response_code": 503,
"response_time_ms": 312
}
Part 7: Docker deployments
# docker-compose.yml
version: "3.8"
services:
nuclei-api:
build: ./nuclei-api
restart: unless-stopped
ports:
- "8080:8080"
environment:
- DATABASE_URL=postgres://nuclei:password@postgres:5432/nuclei_results
- NUCLEI_TEMPLATES_PATH=/root/nuclei-templates
volumes:
- nuclei-templates:/root/nuclei-templates
healthcheck:
test: ["CMD", "curl", "-sf", "http://localhost:8080/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 15s
template-updater:
image: projectdiscovery/nuclei:latest
entrypoint: ["/bin/sh", "-c"]
command:
- "while true; do nuclei -update-templates; sleep 3600; done"
volumes:
- nuclei-templates:/root/nuclei-templates
postgres:
image: postgres:16-alpine
environment:
- POSTGRES_DB=nuclei_results
- POSTGRES_USER=nuclei
- POSTGRES_PASSWORD=password
volumes:
- postgres-data:/var/lib/postgresql/data
volumes:
nuclei-templates:
postgres-data:
Check service health:
docker inspect nuclei-api --format='{{.State.Health.Status}}'
Part 8: Kubernetes deployments
For Nuclei running as a Kubernetes CronJob, add a liveness probe to the scanner pod and a separate Vigilmon monitor for the scan scheduler:
# nuclei-scanner-cronjob.yaml
apiVersion: batch/v1
kind: CronJob
metadata:
name: nuclei-scanner
spec:
schedule: "0 */6 * * *"
jobTemplate:
spec:
template:
spec:
containers:
- name: nuclei
image: projectdiscovery/nuclei:latest
args:
- "-l"
- "/targets/targets.txt"
- "-t"
- "cves/,exposures/"
- "-o"
- "/results/output.json"
- "-json"
volumeMounts:
- name: targets
mountPath: /targets
- name: results
mountPath: /results
restartPolicy: OnFailure
volumes:
- name: targets
configMap:
name: nuclei-targets
- name: results
persistentVolumeClaim:
claimName: nuclei-results-pvc
Monitor CronJob health via a sidecar or result-ingestor service:
# nuclei-health-service.yaml
apiVersion: v1
kind: Service
metadata:
name: nuclei-health
spec:
selector:
app: nuclei-ingestor
ports:
- name: health
port: 8080
targetPort: 8080
type: LoadBalancer
Point Vigilmon at the external IP of nuclei-health to monitor scan liveness from outside the cluster.
Summary
Your Nuclei scanning infrastructure now has layered monitoring:
- API health monitor — confirms the Nuclei API wrapper, binary availability, and database connectivity, polled every 60 seconds by Vigilmon.
- Scan liveness monitor — detects silent scan failures where the API is alive but no scans have completed within the expected window.
- SSL monitor — alerts before the API certificate expires.
- Webhook alerts — DOWN events trigger immediate escalation to your security team, since an offline scanner means unmonitored attack surface.
Security scanning infrastructure that runs silently and unmonitored defeats its own purpose. Vigilmon gives you independent external verification that Nuclei is scanning — so you know when it isn't.
Monitor your Nuclei scanning infrastructure free at vigilmon.online
#nuclei #vulnerability-scanning #security #devops #monitoring #appsec