Prerequisites
- Zeek 5.x or 6.x running on Linux (standalone or cluster mode)
zeekctlavailable on the host- A free account at vigilmon.online
Part 1: Understanding what to monitor in Zeek
Zeek is a passive packet analysis engine. It does not expose an HTTP API, so monitoring happens via:
zeekctl status— checks if the Zeek process is running- Log file freshness — confirms Zeek is writing logs to disk
- Log pipeline health — confirms downstream consumers (Kafka, Elasticsearch, Splunk) are receiving data
All three need to be monitored. A Zeek process that is "running" but not writing logs is as bad as a crashed Zeek.
Part 2: Build a Zeek HTTP health endpoint
Create zeek_health.py
import subprocess
import os
import time
from fastapi import FastAPI, Response
app = FastAPI()
ZEEK_LOG_DIR = os.environ.get("ZEEK_LOG_DIR", "/var/log/zeek/current")
MAX_LOG_STALENESS_SEC = int(os.environ.get("ZEEK_LOG_STALENESS_SEC", "120"))
def run_zeekctl(command: str):
result = subprocess.run(
["zeekctl", command],
capture_output=True,
text=True,
timeout=15,
)
return result.returncode, result.stdout + result.stderr
@app.get("/health")
def health(response: Response):
checks = {}
try:
rc, output = run_zeekctl("status")
if rc == 0 and ("running" in output.lower() or "zeek" in output.lower()):
checks["zeek_process"] = "ok"
else:
checks["zeek_process"] = f"error: {output.strip()[:200]}"
except Exception as e:
checks["zeek_process"] = f"error: {e}"
try:
if os.path.isdir(ZEEK_LOG_DIR):
log_files = [
os.path.join(ZEEK_LOG_DIR, f)
for f in os.listdir(ZEEK_LOG_DIR)
if f.endswith(".log")
]
if log_files:
latest_mtime = max(os.path.getmtime(f) for f in log_files)
age_sec = time.time() - latest_mtime
checks["log_freshness_sec"] = int(age_sec)
if age_sec > MAX_LOG_STALENESS_SEC:
checks["log_status"] = f"warning: no logs written for {int(age_sec)}s"
else:
checks["log_status"] = "ok"
else:
checks["log_status"] = "warning: no log files found"
else:
checks["log_status"] = f"error: log directory not found: {ZEEK_LOG_DIR}"
except Exception as e:
checks["log_status"] = f"error: {e}"
conn_log = os.path.join(ZEEK_LOG_DIR, "conn.log")
try:
if os.path.exists(conn_log):
conn_age = time.time() - os.path.getmtime(conn_log)
checks["conn_log_age_sec"] = int(conn_age)
checks["conn_log"] = "ok" if conn_age < MAX_LOG_STALENESS_SEC else "stale"
else:
checks["conn_log"] = "missing"
except Exception as e:
checks["conn_log"] = f"error: {e}"
process_ok = checks.get("zeek_process") == "ok"
logs_ok = checks.get("log_status") == "ok"
healthy = process_ok and logs_ok
response.status_code = 200 if healthy else 503
return {"status": "ok" if healthy else "degraded", "checks": checks}
Run the health service
pip install fastapi uvicorn
sudo -u zeek uvicorn zeek_health:app --host 0.0.0.0 --port 8094
Add a systemd unit
# /etc/systemd/system/zeek-health.service
[Unit]
Description=Zeek health endpoint
After=network.target
[Service]
User=zeek
Group=zeek
WorkingDirectory=/opt/zeek-health
ExecStart=/usr/local/bin/uvicorn zeek_health:app --host 0.0.0.0 --port 8094
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
sudo systemctl enable zeek-health
sudo systemctl start zeek-health
Verify the endpoint
curl http://localhost:8094/health
Expected response when Zeek is healthy and writing logs:
{
"status": "ok",
"checks": {
"zeek_process": "ok",
"log_freshness_sec": 8,
"log_status": "ok",
"conn_log_age_sec": 8,
"conn_log": "ok"
}
}
Part 3: Set up HTTP monitoring in Vigilmon
Monitor Zeek process and log health
- Log in to vigilmon.online and click Add Monitor.
- Choose HTTP(S) monitor.
- Enter:
http://your-zeek-host:8094/health - Set interval to 1 minute.
- Add a keyword check: must contain
"status":"ok". - Add your alert channel.
- Click Save.
The keyword check catches the "status":"degraded" case where Zeek is technically running but not writing logs — which is the most common silent failure mode.
Monitor Zeek in cluster mode
In a Zeek cluster, add per-node monitors:
| Monitor name | URL | Notes |
|-------------|-----|-------|
| Zeek — Manager node | http://zeek-manager:8094/health | Central process |
| Zeek — Logger node | http://zeek-logger:8094/health | Log aggregation |
| Zeek — Worker 1 | http://zeek-worker1:8094/health | Packet capture |
| Zeek — Worker 2 | http://zeek-worker2:8094/health | Packet capture |
Part 4: Monitor the log pipeline downstream
Zeek logs are typically shipped to Elasticsearch, Splunk, Kafka, or an S3-compatible store. Monitor the downstream pipeline separately.
Elasticsearch index freshness
# elastic_zeek_health.py
import os
import requests
from datetime import datetime, timezone, timedelta
from fastapi import FastAPI, Response
app = FastAPI()
ES_URL = os.environ.get("ELASTICSEARCH_URL", "http://localhost:9200")
MAX_INDEX_AGE_MINUTES = int(os.environ.get("ES_ZEEK_MAX_AGE_MIN", "5"))
@app.get("/health/elasticsearch")
def es_health(response: Response):
try:
now = datetime.now(timezone.utc)
threshold = now - timedelta(minutes=MAX_INDEX_AGE_MINUTES)
query = {
"query": {
"range": {
"@timestamp": {
"gte": threshold.isoformat(),
"lt": now.isoformat()
}
}
},
"size": 0
}
resp = requests.post(
f"{ES_URL}/zeek-*/_count",
json=query,
timeout=10,
)
resp.raise_for_status()
count = resp.json().get("count", 0)
if count == 0:
response.status_code = 503
return {
"status": "stale",
"detail": f"No Zeek documents in Elasticsearch in the last {MAX_INDEX_AGE_MINUTES} minutes",
}
return {"status": "ok", "recent_doc_count": count}
except Exception as e:
response.status_code = 503
return {"status": "error", "detail": str(e)}
Add a Vigilmon monitor for this endpoint with keyword "status":"ok" and a 5-minute interval.
Part 5: Docker deployment
If Zeek runs in Docker, mount the log volume:
# docker-compose.yml
version: "3.8"
services:
zeek:
image: zeek/zeek:latest
network_mode: host
cap_add:
- NET_ADMIN
- NET_RAW
volumes:
- /etc/zeek:/etc/zeek:ro
- zeek-logs:/var/log/zeek
command: ["zeekctl", "start"]
zeek-health:
image: python:3.12-slim
network_mode: host
command: >
sh -c "pip install -q fastapi uvicorn &&
uvicorn zeek_health:app --host 0.0.0.0 --port 8094"
volumes:
- ./zeek_health.py:/app/zeek_health.py
- zeek-logs:/var/log/zeek:ro
working_dir: /app
environment:
ZEEK_LOG_DIR: /var/log/zeek/current
ZEEK_LOG_STALENESS_SEC: "120"
volumes:
zeek-logs:
In container deployments, if zeekctl is not available in the health container, drop the zeek_process check and rely solely on log freshness.
Part 6: Webhook alerts for network visibility gaps
A Zeek outage means your security team is flying blind. Configure Vigilmon to trigger high-priority alerts:
// Example: Node.js webhook receiver
import express from 'express';
const app = express();
app.use(express.json());
app.post('/webhook/vigilmon', async (req, res) => {
const { monitor_name, status, url, checked_at } = req.body;
if (status === 'down') {
console.error('[SECURITY] Zeek network monitoring is DOWN', {
monitor: monitor_name,
url,
at: checked_at,
});
await createSecurityAlert({
title: `Zeek DOWN: ${monitor_name}`,
severity: 'high',
impact: 'Network logs unavailable — threat hunting and detection impaired',
since: checked_at,
});
await flagLogGap({
source: 'zeek',
start: checked_at,
sensor: monitor_name,
});
} else if (status === 'up') {
console.info('[SECURITY] Zeek network monitoring recovered', { monitor: monitor_name });
await resolveSecurityAlert(monitor_name);
}
res.sendStatus(204);
});
Vigilmon sends this payload on status transitions:
{
"monitor_id": "mon_zeek01",
"monitor_name": "Zeek — Core network sensor",
"status": "down",
"url": "http://zeek-host.internal:8094/health",
"checked_at": "2026-07-02T15:30:00Z",
"response_code": 503,
"response_time_ms": 2001
}
Part 7: SSL monitoring for Zeek log destinations
If Zeek ships logs to HTTPS endpoints (Elasticsearch, Splunk HEC, S3):
- In Vigilmon, click Add Monitor.
- Choose SSL monitor.
- Enter each HTTPS host that receives Zeek logs:
elasticsearch.example.comsplunk-hec.example.com
- Set alert threshold to 14 days before expiry.
- Add your alert channel.
An expired certificate on a log destination causes Zeek's log shipper to fail silently, creating a log gap without any process crash.
Summary
Your Zeek deployment now has layered monitoring:
- Process and log freshness endpoint — confirms Zeek is running and actively writing logs, polled every 60 seconds by Vigilmon.
- Downstream pipeline monitors — catches failures in Elasticsearch, Splunk, or Kafka that would silently drop Zeek logs even while the process is healthy.
- Security-aware webhooks — DOWN events create high-priority security alerts and flag log gaps in the SIEM.
- Cluster-level monitoring — one monitor per cluster node so you know exactly which worker or logger went down.
- SSL monitors — alerts before certificate expiry breaks log delivery to downstream systems.
Vigilmon handles check scheduling, multi-region polling, and alert routing. When Zeek goes down, your SOC team knows within 60 seconds — and the log gap is documented before the next incident review.
Monitor your Zeek network monitoring infrastructure free at vigilmon.online
#zeek #networksecurity #nsm #infosec #monitoring