Introduction
Zeek (formerly known as Bro) is one of the most powerful open-source Network Security Monitors (NSMs) available. Unlike traditional IDS tools that match against signature patterns, Zeek takes a fundamentally different approach: it passively observes network traffic and produces structured, high-fidelity logs covering every protocol conversation it sees — DNS, HTTP, SSL/TLS, SSH, SMTP, and dozens more.
The result is a rich audit trail that security teams can query during incident response, feed into a SIEM for correlation, or use as input to machine-learning pipelines for anomaly detection. Zeek's built-in scripting language lets you write custom detection logic at the semantic layer — "alert when a client downloads more than 50 unique executables in five minutes" — rather than wrestling with raw packet offsets.
This guide walks through installing Zeek on an Ubuntu/Debian sensor, configuring it for your environment, writing your first detection script, and shipping logs to a central SIEM.
Prerequisites
Before starting, ensure the following are in place:
- Sensor host: Ubuntu 22.04 LTS or Debian 12 with at least 2 cores and 4 GB RAM. Physical hardware or a VM both work; a dedicated host is recommended for production.
- Mirror/SPAN port: Your switch or hypervisor should mirror the traffic you want to inspect to the sensor's monitoring interface (
eth1in this guide). Zeek only reads traffic — it never injects packets. - Root/sudo access on the sensor.
- Internet access for package installation (or a local mirror).
- (Optional) Filebeat installed to ship Zeek JSON logs to Elasticsearch/Splunk.
Step 1 — Install Zeek
Add the Official Zeek Repository
Zeek publishes prebuilt packages for major distros. Using the official repo ensures you get the latest stable release.
# Install prerequisites
sudo apt-get update
sudo apt-get install -y curl gnupg2 lsb-release
# Add the Zeek GPG key
curl -fsSL https://download.opensuse.org/repositories/security:zeek/xUbuntu_22.04/Release.key \
| gpg --dearmor \
| sudo tee /usr/share/keyrings/zeek-archive-keyring.gpg > /dev/null
# Add the repository
echo "deb [signed-by=/usr/share/keyrings/zeek-archive-keyring.gpg] \
https://download.opensuse.org/repositories/security:zeek/xUbuntu_22.04/ /" \
| sudo tee /etc/apt/sources.list.d/zeek.list
# Install Zeek 7.x (LTS)
sudo apt-get update
sudo apt-get install -y zeekZeek installs to /opt/zeek/. Add its binaries to your PATH:
echo 'export PATH=/opt/zeek/bin:$PATH' >> ~/.bashrc
source ~/.bashrc
zeek --version # Should print: zeek version 7.x.xStep 2 — Configure the Monitoring Interface
Put the Interface into Promiscuous Mode
The monitoring interface must receive all traffic on the segment, not just traffic addressed to it.
# Bring eth1 up in promiscuous mode (immediate, non-persistent)
sudo ip link set eth1 promisc on
ip link show eth1 | grep promisc # Confirm: PROMISC appears in flags
# Disable offloading features that break packet capture accuracy
sudo ethtool -K eth1 rx off tx off gso off gro off lro off tso offTo persist promiscuous mode across reboots, create a systemd network override:
sudo tee /etc/systemd/network/10-eth1.link > /dev/null <<'EOF'
[Match]
Name=eth1
[Link]
Promiscuous=yes
EOF
sudo systemctl restart systemd-networkdTell Zeek Which Interface to Use
Edit the node configuration file:
sudo nano /opt/zeek/etc/node.cfgReplace the default [zeek] stanza with:
[zeek]
type=standalone
host=localhost
interface=eth1For multi-core sensors, enable PF_RING or AF_PACKET workers to distribute load:
[logger]
type=logger
host=localhost
[manager]
type=manager
host=localhost
[proxy-1]
type=proxy
host=localhost
[worker-1]
type=worker
host=localhost
interface=eth1
lb_method=pf_ring
lb_procs=4Step 3 — Configure Local Networks and Log Settings
Declare Your Internal RFC 1918 Ranges
sudo nano /opt/zeek/etc/networks.cfgUncomment and update to match your environment:
# Internal networks — Zeek uses these for direction tagging (originator vs. responder)
10.0.0.0/8 Private RFC 1918 — Class A
172.16.0.0/12 Private RFC 1918 — Class B
192.168.0.0/16 Private RFC 1918 — Class C
Enable JSON Log Output
By default Zeek writes tab-separated logs. JSON makes ingestion into SIEMs significantly easier.
sudo nano /opt/zeek/share/zeek/site/local.zeekAdd the following at the bottom of the file:
# Enable JSON log format
@load policy/tuning/json-logs
redef LogAscii::use_json = T;
# Include timestamps in ISO 8601 format
redef LogAscii::json_timestamps = JSON::TS_ISO8601;
# Rotate logs every hour instead of the default 24h
redef Log::default_rotation_interval = 1hr;Step 4 — Deploy and Start Zeek
Use zeekctl (the Zeek control framework) to manage the deployment lifecycle:
# Apply configuration, install scripts, and check for syntax errors
sudo zeekctl deploy
# Verify status
sudo zeekctl statusExpected output:
Name Type Host Status Pid Started
zeek standalone localhost running 12345 06 Apr 11:23:04
Zeek is now capturing traffic. Logs are written to /opt/zeek/logs/current/:
ls -lh /opt/zeek/logs/current/
# conn.log dns.log http.log ssl.log files.log weird.log notice.log ...Tail Live Connection Data
tail -f /opt/zeek/logs/current/conn.log | python3 -m json.tool | head -60Step 5 — Understanding Key Log Files
| Log File | Contents |
|---|---|
conn.log | Every TCP/UDP/ICMP flow: duration, bytes, state |
dns.log | All DNS queries and responses, including TTLs and answer records |
http.log | HTTP requests: URI, method, status code, user-agent, MIME type |
ssl.log | TLS handshake details: cert CN, cipher suite, validation status |
files.log | Files transferred over HTTP, SMTP, etc. — SHA-256 hashes included |
x509.log | Certificate chain details for every TLS session |
notice.log | Zeek-generated alerts (your scripts write here) |
weird.log | Protocol anomalies — unexpected or malformed traffic |
smtp.log | Email metadata: sender, recipients, subject, attachment names |
Step 6 — Write a Custom Detection Script
Zeek's scripting language lets you write event-driven detection logic. Here's a practical example: detect DNS over non-standard ports (a common data-exfiltration and C2 evasion technique).
Create the script:
sudo nano /opt/zeek/share/zeek/site/detect-dns-tunnel.zeek##! Detect DNS-like traffic on non-standard ports and flag excessively long hostnames.
module DNSTunnel;
export {
redef enum Notice::Type += {
## Raised when DNS traffic is seen on a non-standard port
DNS_On_Nonstandard_Port,
## Raised when a queried hostname exceeds a suspicious length threshold
Long_DNS_Hostname,
};
}
# Threshold: hostnames longer than 60 characters are suspicious
const hostname_len_threshold = 60 &redef;
event dns_request(c: connection, msg: dns_msg, query: string, qtype: count, qclass: count)
{
# Flag DNS queries on ports other than 53
if ( c$id$resp_p != 53/udp && c$id$resp_p != 53/tcp )
{
NOTICE([$note=DNS_On_Nonstandard_Port,
$conn=c,
$msg=fmt("DNS request to non-standard port %s from %s",
c$id$resp_p, c$id$orig_h),
$identifier=cat(c$id$orig_h, c$id$resp_h, c$id$resp_p)]);
}
# Flag unusually long hostnames (common in DNS tunneling)
if ( |query| > hostname_len_threshold )
{
NOTICE([$note=Long_DNS_Hostname,
$conn=c,
$msg=fmt("Long DNS hostname (%d chars): %s queried by %s",
|query|, query, c$id$orig_h),
$identifier=cat(c$id$orig_h, query)]);
}
}Load it in local.zeek:
echo '@load site/detect-dns-tunnel' | sudo tee -a /opt/zeek/share/zeek/site/local.zeekReload Zeek without dropping existing connections:
sudo zeekctl deployDetections now appear in /opt/zeek/logs/current/notice.log.
Step 7 — Ship Logs to a SIEM with Filebeat
Install Filebeat
curl -fsSL https://artifacts.elastic.co/GPG-KEY-elasticsearch \
| gpg --dearmor | sudo tee /usr/share/keyrings/elastic-keyring.gpg > /dev/null
echo "deb [signed-by=/usr/share/keyrings/elastic-keyring.gpg] \
https://artifacts.elastic.co/packages/8.x/apt stable main" \
| sudo tee /etc/apt/sources.list.d/elastic-8.x.list
sudo apt-get update && sudo apt-get install -y filebeatConfigure the Zeek Module
sudo filebeat modules enable zeek
sudo nano /etc/filebeat/modules.d/zeek.yml- module: zeek
connection:
enabled: true
var.paths: ["/opt/zeek/logs/current/conn.log"]
dns:
enabled: true
var.paths: ["/opt/zeek/logs/current/dns.log"]
http:
enabled: true
var.paths: ["/opt/zeek/logs/current/http.log"]
ssl:
enabled: true
var.paths: ["/opt/zeek/logs/current/ssl.log"]
notice:
enabled: true
var.paths: ["/opt/zeek/logs/current/notice.log"]
files:
enabled: true
var.paths: ["/opt/zeek/logs/current/files.log"]Edit /etc/filebeat/filebeat.yml to point at your Elasticsearch instance:
output.elasticsearch:
hosts: ["https://your-elasticsearch:9200"]
username: "filebeat_writer"
password: "${ELASTICSEARCH_PASSWORD}"
ssl.certificate_authorities: ["/etc/ssl/certs/ca.crt"]sudo systemctl enable --now filebeat
sudo filebeat test output # Should print: Connection OKVerification and Testing
Check Zeek is Capturing Traffic
Generate some test traffic, then inspect the logs:
# On another host on your network, make a DNS request and HTTP call
# On the sensor:
grep '"query"' /opt/zeek/logs/current/dns.log | tail -5
grep '"method":"GET"' /opt/zeek/logs/current/http.log | tail -5Test Your Detection Script
Simulate a long DNS hostname query (safe — just a lookup):
# From any host on the monitored segment:
dig "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.example.com" @8.8.8.8Then on the sensor:
grep "Long_DNS_Hostname" /opt/zeek/logs/current/notice.logYou should see a JSON entry with note: "DNSTunnel::Long_DNS_Hostname".
Verify Process Health
sudo zeekctl status # All workers: running
sudo zeekctl diag # Detailed diagnostics and crash checks
sudo zeekctl check # Validate script syntax before deployingTroubleshooting
Zeek shows stopped in zeekctl status
Check the crash log:
sudo zeekctl diag | tail -40
cat /opt/zeek/logs/current/reporter.log # Script errors surface hereMost common cause: a syntax error in a .zeek script. Run sudo zeekctl check after every script change before deploying.
No traffic in logs despite Zeek running
- Verify the SPAN/mirror port is actually delivering packets:
sudo tcpdump -i eth1 -c 20 - Confirm promiscuous mode:
ip link show eth1— thePROMISCflag must be present. - Check that hardware offloading is disabled:
ethtool -k eth1 | grep -E "rx-checksumming|tx-checksumming"
High CPU usage
- Enable AF_PACKET workers (see Step 2 multi-core config).
- Reduce log verbosity: comment out heavy modules in
local.zeek(e.g.,@load policy/protocols/ssl/validate-certsis expensive). - Add a BPF capture filter to exclude irrelevant traffic:
# In node.cfg, add to the worker stanza:
# capture_filter=not host 192.168.100.10 and not port 5353Zeek not generating notice.log
Your script may not be loaded. Confirm:
grep 'detect-dns-tunnel' /opt/zeek/share/zeek/site/local.zeek
sudo zeekctl check # No output means no errors
sudo zeekctl deployLogs not appearing in Elasticsearch
sudo journalctl -u filebeat --since "5 minutes ago"
sudo filebeat test config -c /etc/filebeat/filebeat.ymlCommon issues: incorrect Elasticsearch password, certificate mismatch, or the Zeek module pointing to a rotated log path rather than current/.
Summary
You now have a fully operational Zeek sensor that:
- Passively captures all traffic on your monitored segment via a SPAN/mirror port
- Generates structured JSON logs for every DNS query, HTTP request, TLS session, and file transfer it observes
- Runs custom detection logic in Zeek's event-driven scripting language — illustrated with a DNS tunneling detector
- Ships logs to your SIEM via Filebeat for correlation, dashboarding, and alerting
From here, explore the Zeek package manager (zkg) to install community scripts covering lateral movement, malware C2 patterns, and protocol anomalies:
sudo /opt/zeek/bin/zkg install zeek/corelight/zeek-spicy-ipsec
sudo /opt/zeek/bin/zkg install zeek/sethhall/zeek-community-id
sudo zeekctl deployThe Community ID script (zeek-community-id) is especially valuable: it adds a standardised flow hash to every Zeek log, letting you correlate a connection across Zeek, Suricata, and your firewall logs using a single identifier.
Pair Zeek with Suricata (covered in the Suricata IDS/IPS Deployment guide) for defence-in-depth: Suricata catches known bad via signatures while Zeek gives you the full audit trail and behavioural detection layer.