Zeek deployment¶
Runbook for deploying Zeek as a network security monitor. Zeek runs on a dedicated Hetzner instance, nsm.golemtrust.am, which receives a mirrored copy of all traffic traversing the main Hetzner private network. Dr. Crucible chose Zeek specifically because it produces structured, protocol-aware logs rather than raw packet captures. “We do not want to read every letter,” he said. “We want the catalogue.”
Instance and interface requirements¶
The monitoring instance requires two network interfaces:
eth0: management interface, connected to the private network (10.0.3.1), used for SSH, Graylog log shipping, and Zeek managementeth1: monitoring interface, connected to the mirrored traffic port, no IP address assigned, promiscuous mode
Provision a Hetzner CX41 instance (4 vCPU, 8GB RAM) running Debian 12. The traffic volume at current scale does not justify a larger instance; revisit if additional customer portals are added.
Configure eth1 at boot with no IP and promiscuous mode enabled. Create /etc/network/interfaces.d/eth1:
auto eth1
iface eth1 inet manual
up ip link set $IFACE promisc on
up ip link set $IFACE up
down ip link set $IFACE promisc off
down ip link set $IFACE down
Installation¶
Zeek is not available in the Debian repositories at a current version. Install from the official OpenSUSE Build Service repository:
apt install -y curl gnupg2
echo 'deb http://download.opensuse.org/repositories/security:/zeek/Debian_12/ /' \
| tee /etc/apt/sources.list.d/security:zeek.list
curl -fsSL https://download.opensuse.org/repositories/security:zeek/Debian_12/Release.key \
| gpg --dearmor | tee /etc/apt/trusted.gpg.d/security_zeek.gpg > /dev/null
apt update && apt install -y zeek
Zeek installs to /opt/zeek. Add it to the path:
echo 'export PATH=/opt/zeek/bin:$PATH' >> /etc/profile.d/zeek.sh
source /etc/profile.d/zeek.sh
Configuration¶
Edit /opt/zeek/etc/node.cfg to define the monitoring node:
[zeek]
type=standalone
host=localhost
interface=eth1
Edit /opt/zeek/etc/networks.cfg to define the local network ranges. Zeek uses this to distinguish internal from external traffic:
10.0.0.0/8 Golem Trust private networks
195.201.0.0/16 Hetzner Helsinki public IP range (update if IPs change)
Edit /opt/zeek/etc/zeekctl.cfg:
LogDir = /opt/zeek/logs
SpoolDir = /opt/zeek/spool
MailTo = crucible@golemtrust.am
LogRotationInterval = 3600
LogExpireInterval = 0
Setting LogExpireInterval = 0 disables Zeek’s own log expiry; log rotation and retention are managed by the Graylog integration (see the log integration runbook). Zeek logs are retained in Graylog for 90 days.
Enabling log formats¶
Zeek’s default log format is TSV. Enable JSON output, which Graylog’s Beats input handles more cleanly. Add to /opt/zeek/share/zeek/site/local.zeek:
@load policy/tuning/json-logs
redef LogAscii::use_json = T;
Deploying and starting Zeek¶
Run the Zeek deployment check and start:
zeekctl deploy
This checks the configuration, installs scripts, and starts the Zeek process. On subsequent starts (after a configuration change), run zeekctl deploy again. To start without a full redeploy:
zeekctl start
Check status:
zeekctl status
The output should show running for the zeek node. If it shows crashed, check /opt/zeek/spool/zeek/stderr.log for the error.
Log files¶
Zeek writes one log file per protocol per rotation interval. After the first hour of operation, /opt/zeek/logs/current/ will contain files including:
conn.log: all connection records (source, destination, ports, bytes, duration)http.log: HTTP transactions with method, URI, user agent, response codedns.log: all DNS queries and responsesssl.log: TLS session metadata, certificate details, cipher suitesssh.log: SSH connection metadata (not content; Zeek does not decrypt sessions)smtp.log: SMTP transaction metadatanotice.log: Zeek-generated notices and anomaliesweird.log: protocol anomalies and unexpected behaviour
The weird.log is worth reviewing manually during the first week. It surfaces legitimate quirks in your own traffic that would otherwise generate noise in alert rules.
Zeekctl maintenance¶
Zeek should be restarted weekly to rotate internal state and prevent memory growth. Add to cron on the nsm.golemtrust.am instance:
0 4 * * 1 /opt/zeek/bin/zeekctl restart >> /var/log/zeek-restart.log 2>&1
Check for crashes daily:
0 8 * * * /opt/zeek/bin/zeekctl check >> /var/log/zeek-check.log 2>&1
If zeekctl check reports a problem, Zeek is likely not running and logs have a gap. Investigate before the daily stand-up.
Verification¶
After Zeek has been running for 10 minutes with traffic flowing, inspect the connection log:
zeek-cut id.orig_h id.orig_p id.resp_h id.resp_p proto duration \
< /opt/zeek/logs/current/conn.log | head -20
If the output is empty, either no traffic is reaching eth1 or Zeek is not reading the interface. Check the port mirroring configuration (see the port mirroring runbook) and confirm that eth1 is in promiscuous mode with ip link show eth1.