Skip to main content

Zeek Network Security Monitor: Complete Deployment and Usage Guide 2026

Created: March 10, 2026 Larry Qu 15 min read

Introduction

Zeek (formerly Bro) is an open-source network security monitor that analyzes traffic and generates high-fidelity structured logs — connection summaries, protocol metadata, file extraction records, and security notices. Unlike signature-based IDS/IPS tools, Zeek separates traffic analysis from detection logic: its event engine decodes packets and dispatches events to policy scripts that decide what to log or alert on. This architecture makes Zeek extensible, protocol-aware, and suitable for both real-time monitoring and forensic investigation.

The current release is Zeek 8.2.0 (May 2026), with Zeek 8.0.x as the long-term support line. This guide covers installation, configuration, log analysis, threat detection, custom scripting, cluster deployment, performance tuning, and SIEM integration using 2026-era tooling and best practices.

Understanding Zeek Architecture

Core Architecture

flowchart LR
    A[Network Tap / SPAN] --> B[libpcap / AF_PACKET]
    B --> C[Event Engine]
    C --> D[Event Queue]
    D --> E[Policy Script Interpreter]
    E --> F[Log Writers]
    F --> G[conn.log<br/>http.log<br/>dns.log<br/>ssl.log<br/>...]
    E --> H[Notice Actions]
    H --> I[Alert / Syslog / Email]

Zeek processes packets through a single-threaded event loop: it reads a packet, runs it through protocol analyzers (Spicy-based since Zeek 7+), enqueues generated events, then drains the event queue by executing matching policy script handlers. This serial model guarantees deterministic event ordering but requires clustering to scale beyond a single CPU core at high line rates.

Key Components

Component Role
Event Engine Packet capture, protocol decoding, event generation
Spicy Analyzers Statically-typed DSL protocol parsers (replaces BinPAC)
Policy Scripts Zeek DSL scripts that react to events and define detection logic
Log Writers Output logs in TSV, JSON, or custom formats
zeekctl Process manager for sensor deployment lifecycle
zkg Package manager for community and custom Zeek scripts
Broker / ZeroMQ Cluster messaging layer (ZeroMQ default since Zeek 8.1)

Zeek vs Traditional IDS

Aspect Zeek Suricata / Snort
Detection model Event-driven, behavioral + signature Rule-based, signature-heavy
Output 70+ structured log files Alert events + optional PCAP
Extensibility Full scripting language (Zeek DSL) Rule language with Lua scripting
Protocol parsing Spicy DSL (statically typed) Rust-based or C parsers
File extraction Built-in, policy-configurable Limited, via rules
Typical deployment Sensor + SIEM pipeline Inline IPS or passive IDS

Zeek and Suricata are complementary — many SOCs run both: Suricata for signature-matched alerts and Zeek for comprehensive session logs that analysts pivot on during incident response.

Installation

Zeek 8.2.0 is the current feature release. Binary packages install to /opt/zeek and include the core engine, zeekctl, zkg, and the Spicy toolchain.

Docker Installation (Quick Start)

# Latest feature release
docker pull zeek/zeek:latest

# LTS release
docker pull zeek/zeek:lts

# Specific version
docker pull zeek/zeek:8.2.0

# Run Zeek against a PCAP file
docker run --rm -v $(pwd):/pcap zeek/zeek:latest /pcap/traffic.pcap

# Run Zeek on a live interface (requires privileged mode)
docker run --rm --net=host --cap-add=NET_ADMIN --cap-add=NET_RAW \
  -v /var/log/zeek:/opt/zeek/logs zeek/zeek:latest -i eth0

Docker images are Debian-based, minimal, and published to both Docker Hub and Amazon ECR (public.ecr.aws/zeek/zeek). For production use with custom packages, extend the image with a Dockerfile that installs build dependencies (g++, cmake, libpcap-dev).

Ubuntu / Debian (Binary Packages)

The official OBS repository path uses the security:/zeek namespace (not network:/zeek which is deprecated).

# Add the Zeek OBS repository (Ubuntu 24.04)
echo 'deb https://download.opensuse.org/repositories/security:/zeek/xUbuntu_24.04/ /' \
  | sudo tee /etc/apt/sources.list.d/security:zeek.list

curl -fsSL https://download.opensuse.org/repositories/security:zeek/xUbuntu_24.04/Release.key \
  | gpg --dearmor | sudo tee /etc/apt/trusted.gpg.d/security_zeek.gpg > /dev/null

# Install Zeek 8.0 LTS
sudo apt update && sudo apt install zeek-8.0

# Install latest feature release (8.2)
sudo apt install zeek

RHEL / CentOS / Fedora

# Add OBS repository (RHEL 9)
cat << EOF | sudo tee /etc/yum.repos.d/zeek.repo
[security_zeek]
name=Zeek Repository
baseurl=https://download.opensuse.org/repositories/security:/zeek/RHEL_9/
enabled=1
gpgcheck=1
gpgkey=https://download.opensuse.org/repositories/security:zeek/RHEL_9/Release.key
EOF

sudo dnf install zeek-8.0

Building from Source

# Install build dependencies (Ubuntu)
sudo apt install cmake make gcc g++ flex bison libpcap-dev libssl-dev \
  python3-dev swig zlib1g-dev

# Clone and build
git clone --recurse-submodules https://github.com/zeek/zeek
cd zeek
./configure --prefix=/opt/zeek
make -j$(nproc)
sudo make install

# Add to PATH
echo 'export PATH=/opt/zeek/bin:$PATH' >> ~/.bashrc
source ~/.bashrc

Building from source is useful for custom Spicy analyzers, debug builds, or platforms without binary packages. The configure step detects available features — --enable-debug adds verbose logging for development.

Verifying Installation

zeek --version
# zeek version 8.2.0

Configuration

Initial Setup with zeekctl

Zeekctl manages the Zeek process lifecycle. After binary installation, configure the monitored interface and local networks.

# Edit the node configuration
sudo vi /opt/zeek/etc/node.cfg

Standalone node for a single-interface sensor:

[zeek]
type=standalone
host=localhost
interface=eth0

Cluster node layout (manager, proxy, workers):

[manager]
type=manager
host=192.168.1.10

[proxy-1]
type=proxy
host=192.168.1.10

[worker-1]
type=worker
host=192.168.1.10
interface=eth0
lb_method=pf_ring
lb_procs=4

Network Configuration

Define your monitored networks so Zeek can classify local vs. external hosts:

# /opt/zeek/etc/networks.cfg
10.0.0.0/8          Corporate
192.168.0.0/16      Internal
172.16.0.0/12       DMZ

zeekctl Lifecycle

# Validate configuration
sudo zeekctl check

# Deploy (install, stop if running, start)
sudo zeekctl deploy

# Status
sudo zeekctl status

# Individual commands
sudo zeekctl start
sudo zeekctl stop
sudo zeekctl restart

# View active scripts
sudo zeekctl scripts

# Performance snapshot
sudo zeekctl top
sudo zeekctl capstats

Log Analysis

Zeek generates 70+ log types covering every major protocol. Each log is tab-separated by default, with JSON as the primary alternative format.

Core Log Files

Log Description
conn.log TCP/UDP/ICMP connection summaries
http.log HTTP requests, methods, URIs, MIME types
dns.log DNS queries, responses, and RCODEs
ssl.log TLS handshake parameters, certificates, JA3
ssh.log SSH protocol negotiation, auth attempts
ftp.log FTP commands, credentials, file transfers
smtp.log Email transactions, sender/recipient, headers
dhcp.log DHCP leases, hostnames, vendor classes
files.log Extracted file metadata (MD5/SHA1/SHA256)
notice.log Policy-generated security alerts
weird.log Protocol anomalies and unexpected behavior
intel.log Threat intelligence indicator matches

zeek-cut Utility

The zeek-cut tool (formerly bro-cut) extracts specific columns from Zeek TSV logs. Column names are the header field labels from the log.

# Recent connections: timestamp, src, dst, port, duration, bytes
zeek-cut -d ts id.orig_h id.resp_h id.resp_p proto service \
  duration orig_bytes resp_bytes < /opt/zeek/logs/current/conn.log

# Filter connections with >1MB data transfer
zeek-cut ts uid id.orig_h id.resp_h orig_bytes resp_bytes \
  < conn.log | awk '$4 + $5 > 1000000'

Connection Log Analysis

# Top talkers by connection count
zeek-cut id.orig_h < conn.log | sort | uniq -c | sort -rn | head -10

# External destinations (non-RFC1918)
zeek-cut id.resp_h id.resp_p proto < conn.log \
  | grep -vE '^(10\.|192\.168\.|172\.1[6-9]\.|172\.2[0-9]\.|172\.3[0-1]\.)'

# Longest-running connections
zeek-cut -d ts duration id.orig_h id.resp_h service \
  < conn.log | sort -k2 -rn | head -10

HTTP Log Analysis

# Most requested hostnames
zeek-cut host < http.log | sort | uniq -c | sort -rn | head -10

# HTTP methods distribution
zeek-cut method < http.log | sort | uniq -c | sort -rn

# Large responses (>1MB)
zeek-cut -d ts host uri resp_mime_types resp_body_len \
  < http.log | awk '$5 > 1000000'

# Suspicious file downloads
zeek-cut ts host uri resp_mime_types < http.log \
  | grep -iE 'exe|dll|zip|scr|vbs'

DNS Log Analysis

# Most queried domains
zeek-cut query < dns.log | sort | uniq -c | sort -rn | head -10

# NXDOMAIN responses (potential DNS tunneling)
zeek-cut ts query rcode < dns.log | grep NXDOMAIN

# DNS queries with high QTYPE diversity per client
zeek-cut id.orig_h qtype_name query < dns.log \
  | awk '{print $1, $2}' | sort -u | awk '{print $1}' \
  | uniq -c | sort -rn

# Queries to known suspicious TLDs
zeek-cut ts query < dns.log | grep -iE '\.xyz$|\.top$|\.club$'

SSL / TLS Analysis

# TLS version distribution
zeek-cut version < ssl.log | sort | uniq -c | sort -rn

# Certificate issuers (identifies self-signed certs)
zeek-cut issuer subject < ssl.log | sort | uniq -c | sort -rn

# Weak cipher suites
zeek-cut ts version cipher id.orig_h id.resp_h \
  < ssl.log | grep -iE 'NULL|RC4|MD5|DES|3DES'

# JA3 fingerprint analysis (if JA3 package loaded)
zeek-cut ja3 ja3s < ssl.log | sort | uniq -c | sort -rn

Threat Detection

Zeek detects threats through three mechanisms: built-in policy scripts, custom Zeek scripts, and integrated threat intelligence.

Built-in Detection Policies

Enable detection modules in /opt/zeek/share/zeek/site/local.zeek:

# Scan detection
@load policy/protocols/conn/known-slash24
@load policy/protocols/conn/known-services

# Brute-force detection
@load policy/protocols/ssh/detect-bruteforcing
@load policy/protocols/ftp/detect-password-guessing

# Known hosts and services
@load policy/protocols/conn/known-hosts
@load policy/protocols/conn/known-services

Signature-Based Detection

Zeek supports signature matching for known patterns. Signatures are simpler than Snort rules but effective for protocol-specific IoCs.

# /opt/zeek/share/zeek/site/detections.sig
signature malware-c2 {
    ip-proto == tcp
    payload /.*evil\.com\/c2\/.*/
    event "Potential C2 traffic detected"
}

signature exploit-payload {
    ip-proto == tcp
    payload /\/etc\/passwd|cmd=|wget\s+http/
    event "Exploit pattern detected"
}

Load signatures in local.zeek:

@load-sigs ./detections.sig

JA4 Fingerprinting with Zeek

Zeek supports JA4 network fingerprinting (TLS client/server, HTTP, and TCP fingerprinting) via community packages. JA4 identifies client and server software without decryption.

# Install the JA4 package
sudo zkg install ja4

# Enable in local.zeek
@load ja4

After installation, ssl.log includes JA4 and JA4S fingerprints, and a dedicated ja4.log records HTTP and TCP fingerprints. This is useful for detecting malware C2 that uses custom TLS stacks.

Threat Intelligence Integration

Zeek’s Intel framework compares observed indicators against local or feed-based threat intelligence.

# In local.zeek
redef Intel::read_files += {
    "/opt/zeek/share/zeek/site/intel/domains.txt",
    "/opt/zeek/share/zeek/site/intel/ips.txt"
};

Intel file format (/opt/zeek/share/zeek/site/intel/domains.txt):

#fields: indicator, indicator_type, meta.source, meta.desc
evil.com, Intel::DOMAIN, ThreatFeed, C2 domain
185.130.5.1, Intel::ADDR, AlienVault, Mirai C2

When Zeek sees a match, it generates a notice in intel.log and fires the Intel::match event that custom scripts can intercept.

Custom Scripting

Zeek’s domain-specific scripting language lets you define custom detection logic, logging, and incident response actions.

Scripting Fundamentals

Scripts react to events generated by the event engine. Each event handler receives typed parameters.

# Detect SSH on non-standard ports
event ssh_requested(c: connection, service: count)
{
    if (c$id$resp_p != 22)
        NOTICE([$note=SSH_on_NonStandardPort,
                $msg=fmt("SSH connection to non-standard port: %s:%d",
                         c$id$resp_h, c$id$resp_p),
                $conn=c]);
}

Event names correspond to protocol transitions — ssh_requested fires when the SSH analyzer detects an SSH handshake, http_request each time an HTTP client sends a request, and so on.

Custom Detection Script

Create a script that tracks connections to a known-bad IP range and generates a notice:

# /opt/zeek/share/zeek/site/custom/detect-malware.zeek
module CustomDetect;

export {
    redef enum Notice::Type += {
        Malware_C2_Contact,
    };
}

event connection_established(c: connection)
{
    local bad_subnet = 10.66.66.0/24;
    local resp_h = c$id$resp_h;

    if (resp_h in bad_subnet)
    {
        NOTICE([$note=Malware_C2_Contact,
                $msg=fmt("Connection to known malicious subnet: %s -> %s",
                         c$id$orig_h, resp_h),
                $conn=c]);
    }
}

Enabling Custom Scripts

# Load in local.zeek
@load ./custom/detect-malware

Periodic Actions

Zeek’s scheduling framework runs custom logic at configurable intervals:

# Aggregate and report connection counts every 5 minutes
global conn_count: table[subnet] of count = table();

event new_connection(c: connection)
{
    local orig_subnet = mask_addr(c$id$orig_h, 24.0.0.0);
    ++conn_count[orig_subnet];
}

event zeek_init()
{
    schedule 5min { check_conn_counts() };
}

global check_conn_counts()
{
    for (subnet, count in conn_count)
    {
        if (count > 1000)
            NOTICE([$note=High_Connection_Volume,
                    $msg=fmt("Subnet %s initiated %d connections", subnet, count)]);
    }
    schedule 5min { check_conn_counts() };
}

Deployment Scenarios

Standalone Sensor

Suitable for networks under ~1 Gbps. All Zeek components run on a single host.

# /opt/zeek/etc/node.cfg
[zeek]
type=standalone
host=localhost
interface=eth0

# Deploy
sudo zeekctl deploy

Monitor for packet loss with zeekctl capstats — if any interface shows drops above 0.1%, consider cluster deployment, NIC tuning (disable GRO/GSO), or a faster capture method like AF_PACKET or PF_RING.

Cluster Deployment

For high-throughput environments (1–100+ Gbps), Zeek distributes work across multiple worker processes, optional proxies for event aggregation, loggers for log fan-in, and a manager for coordination.

# /opt/zeek/etc/node.cfg — 3-node cluster
[manager]
type=manager
host=192.168.1.10

[proxy-1]
type=proxy
host=192.168.1.10

[logger-1]
type=logger
host=192.168.1.10

[worker-1]
type=worker
host=192.168.1.11
interface=eth0
lb_method=pf_ring
lb_procs=8

[worker-2]
type=worker
host=192.168.1.12
interface=eth0
lb_method=pf_ring
lb_procs=8

Since Zeek 8.1, cluster communication uses ZeroMQ instead of Broker. ZeroMQ provides lower latency and supports optional encryption for inter-node traffic. The &publish_on_change attribute (Zeek 8.2+) propagates table and set state across the cluster transparently.

# Cluster-wide state propagation (Zeek 8.2+)
global known_bad_hosts: set[addr] = set() &publish_on_change=Cluster::mgr_topic;

Docker / Container Sensor

# Persistent Docker sensor with JSON logging
docker run -d --name=zeek-sensor \
  --restart=always \
  --net=host \
  --cap-add=NET_ADMIN \
  --cap-add=NET_RAW \
  -v /opt/zeek/logs:/opt/zeek/logs \
  -v /opt/zeek/etc/node.cfg:/opt/zeek/etc/node.cfg:ro \
  zeek/zeek:lts

Cloud Deployment

Cloud VPCs require traffic mirroring since physical taps are unavailable.

AWS: Use VPC Traffic Mirroring to forward mirrored traffic from target instances to a Zeek sensor instance. Configure the sensor’s interface to receive GRE-encapsulated mirrored traffic.

# AWS: Receive mirrored traffic on eth0
# Set MTU to handle GRE encapsulation
ip link set eth0 mtu 9001

# /opt/zeek/etc/node.cfg
[zeek]
type=standalone
host=localhost
interface=eth0

GCP: Use Packet Mirroring to copy traffic from selected VM instances to the Zeek collector. The collector receives traffic on a dedicated internal IP.

Azure: Use vTap (virtual network tap) to aggregate traffic from multiple VNICs and stream it to a Zeek appliance VM. Azure does not support GRE encapsulation natively — use ERSPAN type III via the vTap configuration.

Integration

JSON Logging

Zeek logs in TSV by default. Enable JSON for SIEM consumption:

# In local.zeek
@load policy/tuning/json-logs.zeek

Since Zeek 8.2, you can write TSV and JSON simultaneously:

# Write JSON for SIEM, TSV for local archive
event zeek_init()
{
    Log::add_filter(HTTP::LOG, [$name="json-http",
                                 $path="http-json",
                                 $config=table(["use_json"] = "T")]);
}

Elastic Stack (Filebeat + Elasticsearch)

# filebeat.yml
filebeat.inputs:
- type: log
  enabled: true
  paths:
    - /opt/zeek/logs/current/*.log
  json.keys_under_root: true
  json.overwrite_keys: true
  json.add_error_key: false

output.elasticsearch:
  hosts: ["http://localhost:9200"]
  indices:
    - index: "zeek-%{+yyyy.MM.dd}"

The Elastic Zeek integration pack (zeek-8.0.0 in Fleet) defines index templates, ingest pipelines for field mapping, and pre-built dashboards for conn, DNS, HTTP, and TLS data.

Splunk

# inputs.conf
[monitor:///opt/zeek/logs/current/*.log]
sourcetype = zeek:json
index = zeek

# props.conf
[zeek:json]
KV_MODE = json

The Splunk Add-on for Zeek (available on Splunkbase) provides field extractions, dashboards, and data model acceleration for all core log types.

CEF Output for SIEMs

# In local.zeek
@load policy/frameworks/cef

Zeek’s CEF framework formats notices as Common Event Format, parseable by most enterprise SIEMs (ArcSight, QRadar, LogRhythm). Combine with syslog forwarding for centralized alerting.

Package Management with zkg

The Zeek Package Manager (zkg) installs, removes, and updates community-contributed scripts and Spicy analyzers.

# List installed packages
zkg list

# Search the package registry
zkg search ja4

# Install a package
zkg install zeek/ja4

# Update all packages
zkg update

# Remove a package
zkg remove ja4

Packages install to /opt/zeek/share/zeek/site/ and load via @load directives in local.zeek. The registry at packages.zeek.org hosts 270+ contributed packages covering protocol analyzers (WireGuard, QUIC, MQTT), threat intelligence feeds, and visualization tools.

Zeek 8.x Feature Highlights

Zeek 8.0 (August 2025, LTS) through 8.2 (May 2026) introduced several architectural changes:

Feature Version Impact
ZeroMQ cluster messaging 8.1 Replaced Broker — lower latency, smaller memory footprint
Customizable flow tuples 8.0 Cluster workers can hash on arbitrary connection fields
&publish_on_change 8.2 Transparent state propagation across cluster nodes
ZeroMQ encryption 8.2 Cluster traffic confidentiality with TLS
IGMP analyzer 8.2 First-class IGMP protocol parsing
Prometheus metrics on 127.0.0.1 8.2 Default bind changed from 0.0.0.0 for security
JA4 fingerprinting packages 8.1 Community packages for JA4/JA4S/JA4X
ZAM optimizer improvements 8.1-8.2 Faster script execution, memory leak fixes
Spicy JIT compilation 8.0+ Protocol parsers compile to native code
Windows support (improved) 8.2 Bug fixes, Spicy support in progress

Performance Optimization

Capture Interface Tuning

# Increase ring buffer
ethtool -G eth0 rx 4096 tx 4096

# Disable offloading that Zeek's engine re-does
ethtool -K eth0 gro off gso off tso off lro off

# Increase socket backlog
sysctl -w net.core.rmem_default=26214400
sysctl -w net.core.rmem_max=67108864

zeekctl Performance Config

# /opt/zeek/etc/zeekctl.cfg
NumWorkers = 4          # Match physical core count
NumProxyProcs = 2
NumLoggerProcs = 1
buffer_size = 128       # Capture buffer per worker (MB)
pin_cpus = 2,3,4,5      # CPU affinity for workers

Connection Tracking Limits

# In local.zeek
redef max_connection_state = 100000;
redef default_rotation_interval = 1hr;

Packet Loss Diagnosis

# Check per-interface drops
zeekctl capstats

# Kernel packet loss
netstat -s | grep -E "packet receive errors|overflow"

# Zeek worker drops (from stdout.log)
grep -i "drop" /opt/zeek/logs/current/stdout.log

Troubleshooting

Zeek Not Generating Logs

# Verify process is running
zeekctl status

# Check for configuration errors
zeekctl check

# View startup errors
cat /opt/zeek/logs/current/stdout.log
cat /opt/zeek/logs/current/stderr.log

# Confirm interface is receiving packets
tcpdump -i eth0 -c 10

High Packet Loss

# Examine capture stats
zeekctl capstats

# Increase worker count in node.cfg
# Disable NIC offloading (see Performance section)
# Switch to PF_RING or AF_PACKET v3

Memory Exhaustion

# Reduce max connection tracking
redef max_connection_state = 50000;

# Disable unused protocol analyzers
# In local.zeek:
@unload protocols/ftp
@unload protocols/smtp

# If using ZAM, check for the fixed table-iteration memory leak (8.2+)

Debugging Custom Scripts

# Run Zeek in debug mode against a PCAP
zeek -d -C -r /tmp/trace.pcap /opt/zeek/share/zeek/site/local.zeek

# Print event stream
zeek -e 'event zeek_init() { print("Zeek ready"); }' -C -r /tmp/trace.pcap

# List all loaded scripts
zeekctl scripts

# Zeek's print() output goes to stdout — redirect with zeek -b
zeek -b -r /tmp/trace.pcap /opt/zeek/share/zeek/site/local.zeek 2>&1

Best Practices

Deployment Checklist

  1. Choose monitoring location — tap or SPAN port at network chokepoint (core switch uplink, internet gateway)
  2. Size the sensor — 1 CPU core per ~500 Mbps of traffic, 1 GB RAM per 10K concurrent connections
  3. Enable at least the core detection policies — SSH brute-force, known services, known hosts, scan detection
  4. Configure log rotationdefault_rotation_interval = 1hr keeps log files manageable
  5. Enable JSON logging — required for SIEM ingestion
  6. Verify packet capture — run zeekctl capstats and confirm zero drops after 24 hours
  7. Integrate with SIEM — send Zeek logs to Elasticsearch, Splunk, or your central log platform
  8. Test with known IoCs — load a test Intel feed and verify intel.log fires

Operational Procedures

# Daily health check
zeekctl status
df -h /opt/zeek/logs          # Disk usage
zeekctl capstats              # Packet loss
tail -5 /opt/zeek/logs/current/stderr.log

# Weekly
zeekctl top                   # Resource usage
zkg update                    # Package updates

# Monthly
zeekctl scripts               # Verify loaded scripts
zeekctl diag                  # Full diagnostics

Security Hardening

# Restrict log access
sudo chown -R root:zeek /opt/zeek/logs
sudo chmod -R 750 /opt/zeek/logs

# Run zeekctl as non-root (add user to zeek group)
sudo usermod -aG zeek $USER

# Secure cluster communication (Zeek 8.2+)
# Enable ZeroMQ encryption in zeekctl.cfg
ClusterEncryption = TLS
ClusterCA = /opt/zeek/etc/cluster-ca.pem
ClusterCertificate = /opt/zeek/etc/cluster-cert.pem

# Disable the default Prometheus on all interfaces
# MetricsAddress = 127.0.0.1 (this is default since 8.2)

Resources

Comments

👍 Was this article helpful?