Zeek Architecture
Zeek (formerly Bro) is a passive network analysis framework that reads packet streams and generates structured log files describing the network activity it observes. Unlike Wireshark (which shows you every packet) or Suricata (which fires signatures), Zeek produces application-layer summaries: one row in conn.log per TCP connection, one row in dns.log per DNS query, one row in ssl.log per TLS session. Zeek is what you run 24/7 on your network tap to build the permanent log record of all network activity.
You're building the detection capability for a new SOC. You have a 10 Gbps SPAN port from the core switch. You deploy Zeek on a server with a 25 Gbps NIC and enough CPU. Zeek processes all traffic and produces structured logs that are ingested into Elasticsearch. A query that would require reading 4 TB of PCAP (impractical) instead queries Zeek conn.log with 30 days of data (fast, searchable). When an alert fires, you retrieve the relevant raw PCAP for the specific flow in question — the PCAP is evidence; the Zeek logs are your searchable index.
Zeek Architecture
Zeek Architecture
═══════════════════════════════════════════════════════════════════
Input layer:
├── Live interface (PF_RING, AF_PACKET, or standard kernel socket)
├── PCAP file (for offline analysis: zeek -r capture.pcap)
└── Cluster input: multiple workers reading from shared queue
Processing layer:
├── Packet capture → reassembly engine
│ TCP stream reassembly: handles out-of-order, retransmits
│ UDP handling: per-packet
│ IP fragmentation: reassembly before protocol analysis
├── Protocol analyzer stack:
│ L3: IP, IPv6, ICMP
│ L4: TCP, UDP
│ L7: HTTP, DNS, TLS, SMB, SMTP, FTP, SSH, RDP, Kerberos, ...
│ Protocol detection: DPI identifies protocol regardless of port
└── Script engine:
Zeek scripts: event-driven policy language
Events: connection_state_remove, dns_request, http_reply, ...
Built-in: default scripts generate all standard log files
Output layer (structured logs, tab-separated):
├── conn.log: one row per connection (all protocols)
├── dns.log: all DNS queries and responses
├── ssl.log: TLS handshake metadata (SNI, cert, JA3, cipher)
├── http.log: HTTP requests and responses
├── files.log: files transferred (with hash)
├── x509.log: certificate details for TLS connections
├── weird.log: protocol anomalies and unusual behaviors
├── notice.log: detection events from scripts
├── smtp.log: SMTP sessions
├── kerberos.log: Kerberos authentication
└── + 30+ additional protocol-specific logs
Cluster architecture (high-throughput):
├── Manager: controls cluster, receives log data
├── Logger: writes logs to disk
├── Proxy: distributes state across workers
└── Workers (N): each reads a subset of packets, processes independently
Zeek Deployment
#!/bin/bash
# Zeek deployment and basic configuration
echo "=== Install Zeek (Ubuntu/Debian) ==="
# Install from official packages or build from source
# Official packages at: https://software.opensuse.org/download.html?project=security:zeek
apt-get install -y zeek
echo ""
echo "=== Basic Zeek run on PCAP ==="
zeek -r capture.pcap
echo ""
echo "=== Zeek run with specific scripts ==="
zeek -r capture.pcap local # Load default local.zeek
zeek -r capture.pcap /opt/zeek/share/zeek/policy/frameworks/notice/main.zeek
echo ""
echo "=== Zeek run on live interface ==="
zeek -i eth0 local
echo ""
echo "=== Configure Zeek for production (/opt/zeek/etc/node.cfg) ==="
cat > /opt/zeek/etc/node.cfg << 'CONF'
[logger]
type=logger
host=localhost
[manager]
type=manager
host=localhost
[proxy-1]
type=proxy
host=localhost
[worker-1]
type=worker
host=localhost
interface=eth0
lb_method=pf_ring
lb_procs=8
CONF
echo ""
echo "=== Configure log rotation (/opt/zeek/etc/zeekctl.cfg) ==="
grep -E "LogRotationInterval|LogExpireInterval|LogDir" /opt/zeek/etc/zeekctl.cfg 2>/dev/null
# LogRotationInterval = 3600 (hourly rotation, good for log shipping)
# LogExpireInterval = 0 (Zeek doesn't delete logs — manage with external rotation)
# LogDir = /opt/zeek/logs
echo ""
echo "=== Start/stop Zeek cluster ==="
zeekctl deploy # Deploy configuration changes
zeekctl status # Show worker status
zeekctl start # Start
zeekctl stop # Stop
echo ""
echo "=== Configure local.zeek for custom tuning ==="
cat >> /opt/zeek/share/zeek/site/local.zeek << 'ZEEK'
# Load JA3 fingerprinting
@load policy/protocols/ssl/ja3
# Load file extraction for suspicious files
@load policy/frameworks/files/extract-all-files
# Load C2 detection scripts
@load policy/misc/detect-traceroute
# Increase connection table size for high-traffic environments
redef tcp_SYN_timeout = 30 secs;
redef tcp_session_timer = 60 secs;
ZEEK
Think of Zeek and PCAP the way a database index relates to the underlying data. Zeek's structured logs let you query "show me all connections to port 443 from 10.0.0.50 over the last 30 days" in milliseconds — a query that would require reading terabytes of PCAP sequentially. But Zeek logs are summaries: they tell you the connection existed, how long it lasted, how many bytes moved, and what the TLS handshake looked like — but not the full payload. When a Zeek query returns a suspicious connection, you retrieve that specific flow's packets from your PCAP archive for deep inspection. The operational workflow is always: Zeek for hunting and detection → PCAP for forensic confirmation. Never delete the PCAP until retention requirements are met, even when Zeek is deployed. They're complementary, not substitutable.
Q & A
Q: Zeek is dropping packets at 5 Gbps even though the server has enough CPU. What's wrong?
Packet drops in Zeek at high throughput are almost always an I/O issue, not a CPU issue. Common causes and fixes: (1) Kernel socket buffer too small: default Linux socket receive buffers (256 KB) are too small for high-speed capture. Fix: sysctl -w net.core.rmem_max=268435456 and set net.core.netdev_budget=600. (2) Not using PF_RING or AF_PACKET: standard sockets force every packet through the full kernel networking stack. PF_RING (commercial but high-performance) or AF_PACKET with TPACKET_V3 (ring buffers) dramatically reduces kernel overhead. Zeek workers should be configured with lb_method=pf_ring or lb_method=af_packet. (3) IRQ affinity: the NIC's interrupt requests should be pinned to specific CPU cores, not the same cores processing Zeek. Use set_irq_affinity.sh to configure this. (4) Too many Zeek scripts: the default local.zeek loads many scripts that add processing overhead. Disable scripts you don't use. Profile with zeek -b -r large_capture.pcap scripts ... to measure per-script overhead. (5) Log write bottleneck: Zeek's logging can saturate disk I/O on 1 Gbps networks. Write logs to a RAM disk or fast SSD, and ship logs to Elasticsearch or Kafka asynchronously rather than writing them synchronously to the local filesystem.