EVE JSON
EVE (Extensible Event Format) JSON is Suricata's structured output format. All event types — alerts, DNS queries, HTTP transactions, TLS handshakes, flow summaries — are written to a single eve.json file as newline-delimited JSON, with an event_type field distinguishing them. EVE JSON is what you ship to your SIEM, query with jq for triage, and analyze in Python for hunting. This chapter covers EVE JSON field schemas and practical query patterns.
A Suricata alert fires for SID 2024218 (ET MALWARE Cobalt Strike). The analyst opens eve.json, filters to the alert event, then pivots to the TLS event for the same flow_id to get the JA3 hash and certificate subject. They extract the community_id from the alert and search Zeek's conn.log for the same community_id to get the full connection duration and byte counts. All from one file using jq.
EVE JSON Schema Reference
EVE JSON — Key Fields by Event Type
═══════════════════════════════════════════════════════════════════
Common fields (all events):
timestamp "2024-01-15T14:23:17.123456+0000"
flow_id Integer: all events from same connection share this
event_type "alert" | "dns" | "http" | "tls" | "flow" | "smtp" | ...
src_ip / dest_ip
src_port / dest_port
proto "TCP" | "UDP" | "ICMP"
community_id Community ID hash (if enabled)
event_type = "alert":
alert.signature_id SID of the matching rule
alert.signature Rule msg field
alert.category classtype of the rule
alert.severity 1-3 (1=high, 2=medium, 3=low)
alert.action "allowed" | "blocked" (IPS mode)
payload Base64-encoded packet payload (if payload:yes in config)
payload_printable Printable ASCII version
event_type = "tls":
tls.sni Server Name Indication
tls.version "TLS 1.3" etc.
tls.cipher Cipher suite
tls.ja3.hash JA3 fingerprint
tls.ja3s.hash JA3S fingerprint
tls.subject Server cert subject
tls.issuerdn Server cert issuer
tls.fingerprint Cert SHA1 fingerprint
tls.notbefore Cert validity start
tls.notafter Cert validity end
event_type = "dns":
dns.type "query" | "answer"
dns.rrname Domain name queried
dns.rrtype "A" | "AAAA" | "MX" | "TXT" | ...
dns.rcode "NOERROR" | "NXDOMAIN" | ...
dns.answers Array of answer records (if type=answer)
event_type = "flow":
flow.pkts_toserver / flow.pkts_toclient
flow.bytes_toserver / flow.bytes_toclient
flow.start / flow.end
flow.state "established" | "closed" | "new"
app_proto Detected application protocol
EVE JSON Queries with jq
#!/bin/bash
EVE="${1:-/var/log/suricata/eve.json}"
echo "=== All alert SIDs with counts ==="
jq -r 'select(.event_type=="alert") | .alert.signature_id' "$EVE" | \
sort | uniq -c | sort -rn | head -20
echo ""
echo "=== Alerts in last hour ==="
ONE_HOUR_AGO=$(date -u -d '1 hour ago' '+%Y-%m-%dT%H:%M:%S')
jq -r --arg cutoff "$ONE_HOUR_AGO" \
'select(.event_type=="alert" and .timestamp > $cutoff) |
[.timestamp, .src_ip, .dest_ip, .dest_port, .alert.signature] | @tsv' \
"$EVE" | head -50
echo ""
echo "=== TLS events without SNI (potential C2) ==="
jq -r 'select(.event_type=="tls") |
select(.tls.sni == null or .tls.sni == "") |
[.timestamp, .src_ip, .dest_ip, .dest_port, (.tls.ja3.hash // "no-ja3")] | @tsv' \
"$EVE" | head -20
echo ""
echo "=== JA3 matching known-bad hashes ==="
KNOWN_BAD='["51c64c77e60f3980eea90869b68c58a8","de9f2c7fd25e1b3afad3e85a0226a4c4"]'
jq -r --argjson bad "$KNOWN_BAD" \
'select(.event_type=="tls") |
select([.tls.ja3.hash] | inside($bad)) |
[.timestamp, .src_ip, .dest_ip, .tls.sni, .tls.ja3.hash] | @tsv' \
"$EVE" | head -20
echo ""
echo "=== DNS NXDOMAIN rate by source (DGA detection) ==="
jq -r 'select(.event_type=="dns") |
select(.dns.type=="answer" and .dns.rcode=="NXDOMAIN") |
.src_ip' "$EVE" | sort | uniq -c | sort -rn | head -10
echo ""
echo "=== Large outbound flows (>10MB) ==="
jq -r 'select(.event_type=="flow") |
select(.flow.bytes_toserver > 10000000) |
[.src_ip, .dest_ip, .dest_port, .flow.bytes_toserver, .flow.bytes_toclient] | @tsv' \
"$EVE" | sort -t$'\t' -k4 -rn | head -10
echo ""
echo "=== Pivot: from alert to TLS context ==="
# Find a specific alert's flow_id, then get TLS metadata for that flow
TARGET_SID=2024218
FLOW_ID=$(jq -r --argjson sid "$TARGET_SID" \
'select(.event_type=="alert" and .alert.signature_id==$sid) | .flow_id' \
"$EVE" | head -1)
if [ -n "$FLOW_ID" ]; then
echo "Alert flow_id: $FLOW_ID"
echo "TLS context for this flow:"
jq -r --argjson fid "$FLOW_ID" \
'select(.flow_id==$fid and .event_type=="tls") |
[.tls.sni, .tls.ja3.hash, .tls.subject, .tls.issuerdn, .tls.fingerprint] | @tsv' \
"$EVE"
fi
Every EVE JSON event from the same network flow shares the same flow_id integer. When you see a Suricata alert, the flow_id in that alert lets you immediately retrieve all other protocol events from that same connection: the TLS handshake metadata (JA3, SNI, cert), the HTTP request details (URI, User-Agent, headers), the DNS query that preceded the connection, and the flow summary with total byte counts. This is how you go from "rule fired on SID X" to "here's the complete network context for that event" in one query: jq -r --argjson fid "$FLOW_ID" 'select(.flow_id==$fid)' eve.json. If you also correlate with Zeek via Community ID (both Suricata and Zeek can compute it), you get the cross-tool join: Suricata alert → same flow in Zeek ssl.log → same connection in Zeek conn.log → same source IP in Zeek dns.log. The Community ID is the bridge that makes multi-tool correlated investigation practical.
Q & A
Q: Suricata's eve.json is growing at 50 GB/day. How do I manage this?
At 50 GB/day, direct file storage becomes a problem within days. Production solutions: (1) Selective event types: in suricata.yaml's eve-log section, disable event types you don't need. If you only care about alerts and TLS, disable dns/http/flow output from eve.json — each of these can generate 5-10× as many events as alerts alone. DNS logging is especially high-volume; put it in a separate file or disable it if Zeek covers it. (2) Kafka as the primary sink: configure Suricata to write to a Kafka topic instead of a file. Kafka handles the buffering and consumers (Elasticsearch, Splunk) pull from it at their own rate. No file rotation to manage, and you can add multiple consumers without changing Suricata. (3) Flow events to a separate shorter-retention store: flow events are voluminous but less valuable after 24 hours. Write them to a separate daily index with 7-day retention, while keeping alert events for 90 days. (4) Compression: if writing to files, enable filesystem-level compression (ZFS with LZ4, or gzip via logrotate). EVE JSON compresses to about 15-20% of original size with gzip due to highly repetitive field names. (5) Alert-only mode: for high-traffic environments where you have Zeek for protocol logging, configure Suricata for alert events only — Suricata does the detection, Zeek does the logging, and you avoid duplication.