Zeek Network Security Monitoring on Linux: Scripting, JA4 Fingerprints, and Threat Hunting in 2026

Install Zeek 7.x on Linux, tune AF_PACKET for line-rate capture, write your first detection script, use JA4 TLS fingerprints, and stitch Zeek into a SIEM for real threat hunting.

Updated: August 19, 2026

Zeek is a passive, open-source network security monitor for Linux that turns raw traffic into structured, high-fidelity logs and lets defenders write detection logic in a domain-specific scripting language. Where a signature-based IDS answers "did this packet match a rule?", Zeek answers "what actually happened on the wire, minute by minute, and does it match how this host normally behaves?" In this guide I'll walk you through installing Zeek 7.x on Linux, tuning AF_PACKET for line-rate capture, writing your first detection scripts, using JA4/JA4S TLS fingerprints, and stitching Zeek into an existing SIEM. I'm writing this from the offensive side: every step maps to something an attacker on your network is actively trying to hide.

  • Zeek isn't an IDS competitor to Suricata. It's an event and metadata engine. Run both; Suricata for signatures, Zeek for behavior and encrypted-traffic analysis.
  • Zeek 7.x ships JA4/JA4S TLS fingerprinting through community packages, which survives certificate rotation and catches C2 clients that classic JA3 misses.
  • AF_PACKET v3 with kernel fanout replaces PF_RING for most Linux deployments and hits 10 Gbps on commodity NICs without kernel patches.
  • The Intel:: framework ingests MISP, CrowdStrike, and OpenCTI feeds and matches indicators against every seen host, URI, and hash in real time.
  • Zeek's conn.log, ssl.log, dns.log, and files.log answer 80 % of the "what did that host do" questions that come up during incident response.
  • Deploy in a cluster (manager + workers + logger + proxy) as soon as you exceed one 10 Gbps link or want per-CPU scaling.

What is Zeek used for on Linux?

Zeek is used on Linux as a passive network sensor that reconstructs TCP, UDP, and application-layer sessions (HTTP, DNS, TLS, SSH, SMB, Kerberos, and dozens more) into structured logs and lets analysts run scripts against those events in real time. It is not a firewall, it doesn't block anything, and it doesn't rely on signatures. In a typical blue-team deployment Zeek sits on a SPAN port, TAP, or ERSPAN destination and writes JSON logs to a local directory; a Filebeat or Vector agent then forwards them to Elastic, OpenSearch, or Wazuh for correlation.

So, why does Zeek matter in 2026? Traffic is almost entirely encrypted. Rules-based tools can only inspect what they can decrypt, but Zeek's TLS handler produces certificate metadata, JA4 fingerprints, and connection-shape features that survive TLS 1.3 and Encrypted Client Hello. That's why every commercial NSM (Corelight, Darktrace's ETA layer, Trellix) is either built on Zeek or reimplements what Zeek does. Honestly, when I do a red-team engagement, Zeek is the tool I most fear on the defender side, because it doesn't care that my beacon is HTTPS: it fingerprints my TLS client and notes that no other host has ever spoken that fingerprint before.

Is Zeek better than Suricata?

Zeek isn't better than Suricata; the two answer different questions and are usually deployed together in a defence-in-depth stack. Suricata is a signature-based IDS/IPS that fires on packet-level rules (ET Open, Emerging Threats Pro, custom SIDs). Zeek is a metadata generator and behavioral engine. The comparison table below is the one I actually use when scoping a customer's NSM refresh.

DimensionZeek 7.xSuricata 7.x
Primary outputStructured protocol logs + custom eventsAlerts + optional protocol logs
Detection modelScriptable behavior, stateful, per-host baseliningSignature match, some Lua scripting
TLS 1.3 visibilityCerts, SNI, JA4/JA4S, ALPN, JARM, connection shapeCerts, SNI, JA3 (JA4 in progress)
Blocking (IPS mode)No, passive onlyYes, inline nfqueue/AF_PACKET
Line rate on 10 GbpsCluster of 8–16 workersSingle instance with AF_PACKET fanout
Learning curveHigher (Zeek script language)Lower (write Snort-style rules)
Best forThreat hunting, IR, encrypted C2 detectionKnown-CVE exploitation, malware droppers

Run both. Point them at the same AF_PACKET fanout group so they see identical traffic, and correlate their logs in your SIEM by 5-tuple and community_id. If you haven't yet built a stacked detection pipeline, my earlier walkthrough of building a multi-layer Linux intrusion detection system with AIDE, auditd, Wazuh, and Suricata is the right starting point. Zeek slots in as the network-metadata layer.

Install Zeek 7.x on Ubuntu, Debian, and RHEL

The official Zeek installation docs maintain OBS repositories for the current stable and LTS branches. I recommend the LTS on production sensors and current stable on staging. Don't run distro-packaged Zeek. It's invariably years behind and lacks JA4 support.

Ubuntu 24.04 and Debian 13

echo 'deb http://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
sudo apt update
sudo apt install -y zeek-lts zeek-lts-core-dev zkg
echo 'export PATH=/opt/zeek/bin:$PATH' | sudo tee /etc/profile.d/zeek.sh

RHEL 9, AlmaLinux 10, Rocky 10

sudo dnf config-manager --add-repo \
  https://download.opensuse.org/repositories/security:zeek/RHEL_9/security:zeek.repo
sudo dnf install -y zeek-lts zeek-lts-core-devel zkg
sudo firewall-cmd --permanent --add-service=zeek || true

Verify the install and initialise the package manager:

zeek --version
# zeek version 7.0.7

sudo -u zeek zkg autoconfig
sudo -u zeek zkg refresh

Line-rate capture with AF_PACKET and kernel fanout

For years Zeek deployments defaulted to PF_RING with a kernel module. Since kernel 4.19 stabilised AF_PACKET v3 with fanout groups, the module is unnecessary for the vast majority of workloads and simplifies compliance because you no longer ship an out-of-tree kernel driver. Configure node.cfg to use AF_PACKET and pin workers to specific CPUs:

# /opt/zeek/etc/node.cfg
[manager]
type=manager
host=localhost

[logger]
type=logger
host=localhost

[proxy-1]
type=proxy
host=localhost

[worker-1]
type=worker
host=localhost
interface=af_packet::eno2
lb_method=custom
lb_procs=8
pin_cpus=4,5,6,7,8,9,10,11
af_packet_fanout_id=23
af_packet_fanout_mode=AF_Packet::FANOUT_HASH

On the sensor NIC, disable offloads that reassemble or rewrite frames before Zeek sees them. Otherwise your conn.log will show impossible packet sizes and TCP state transitions.

for feat in tso gso gro lro rx tx sg; do
  sudo ethtool -K eno2 $feat off 2>/dev/null || true
done
sudo ethtool -G eno2 rx 4096 tx 4096
sudo ip link set eno2 promisc on

Set the CPU governor to performance and disable Intel TurboBoost throttling. A Zeek worker that hits scheduler latency drops packets silently. The capture_loss.log is the first log I open when investigating "we don't see that host" complaints. I hit this exact issue at a Fortune 500 shop last year: everything looked healthy, but a lazy governor was costing us roughly 3% of packets during peak hours.

The logs that matter: conn, ssl, dns, files, weird

Out of the box Zeek writes about 40 log files. In practice, five of them answer most incident-response questions. Enable JSON output (Zeek's TSV format is human-readable but painful for SIEM ingestion):

# /opt/zeek/share/zeek/site/local.zeek
@load policy/tuning/json-logs.zeek
@load policy/protocols/ssl/log-hostcerts-only.zeek
@load policy/frameworks/files/hash-all-files.zeek

The core five:

  • conn.log: one row per completed flow. 5-tuple, duration, byte counts, TCP state history, community_id. This is the correlation key across every other tool.
  • ssl.log: TLS handshakes. SNI, issuer, subject, JA4, JA4S, ALPN, version, cipher. If encrypted-traffic analysis is your beat, this is your bread and butter.
  • dns.log: every query and response. Feed this to your DGA detector; also indispensable when hunting DNS-over-HTTPS abuse (attackers still leak bootstrap A queries).
  • files.log: anything Zeek was able to extract or hash across HTTP, SMTP, SMB, FTP, and IRC. Pipe SHA-256 to VirusTotal or an in-house YARA scanner.
  • weird.log: protocol anomalies. Attackers running custom C2 over "HTTP" that isn't quite HTTP show up here first.

Writing your first Zeek script

Zeek's power is its scripting language. Every packet, every connection state change, every reconstructed application-layer event fires a handler you can hook. Here's a small but genuinely useful script: it flags any internal host that resolves a new domain and immediately opens a TLS connection to it. That's a common shape for browser-delivered malware.

# /opt/zeek/share/zeek/site/scripts/new-domain-then-tls.zeek
@load base/frameworks/notice
@load base/protocols/dns
@load base/protocols/ssl

module NewDomainTLS;

export {
    redef enum Notice::Type += { New_Domain_Then_TLS };
    const watch_window = 30sec &redef;
    const internal_nets: set[subnet] = {
        10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16
    } &redef;
}

global recently_resolved: table[addr, string] of time
    &create_expire=1min;

event DNS::log_dns(rec: DNS::Info) {
    if (rec$qtype_name != "A" || ! rec?$answers) return;
    for (i in rec$answers) {
        recently_resolved[rec$id$orig_h, rec$query] = network_time();
    }
}

event SSL::log_ssl(rec: SSL::Info) {
    if (rec$id$orig_h !in internal_nets) return;
    if (! rec?$server_name) return;
    if ([rec$id$orig_h, rec$server_name] !in recently_resolved) return;
    local delta = network_time() - recently_resolved[rec$id$orig_h, rec$server_name];
    if (delta <= watch_window) {
        NOTICE([$note=New_Domain_Then_TLS,
                $conn=rec$conn,
                $msg=fmt("%s resolved %s and connected via TLS within %s",
                        rec$id$orig_h, rec$server_name, delta),
                $identifier=cat(rec$id$orig_h, rec$server_name)]);
    }
}

Load it in local.zeek, run zeekctl deploy, and every hit lands in notice.log. That file is where I want your SIEM correlation rules to fire, not conn.log, which is far too noisy.

JA4 and JA4S TLS fingerprinting

JA3 has served defenders well for six years, but attackers learned to randomise cipher suites and defeat it. JA4, published by FoxIO under a BSL license in 2023, is now the community standard. The JA4 reference implementation ships a Zeek package that adds ja4, ja4s, ja4h, and ja4x fields to ssl.log and http.log. Install it with zkg:

sudo -u zeek zkg install zeek/foxio-llc/ja4
echo "@load packages" | sudo tee -a /opt/zeek/share/zeek/site/local.zeek
sudo -u zeek zeekctl deploy

Now every TLS handshake gets a stable fingerprint that captures the TLS version, cipher list, extension list sorted, ALPN, and signature algorithms. Two properties matter for detection: JA4 is deterministic across sessions from the same client library, and it's easy to pivot on. If your ssl.log shows a JA4 of t13d1516h2_8daaf6152771_02713d6af862 hitting five different SNI names in an hour, and no other host in your fleet shares it, you've found a Cobalt Strike beacon, a Sliver implant, or a red teamer, often before the C2 domain hits any threat feed.

Pair JA4 with community_id so you can cross-reference the same flow across Zeek, Suricata, and any endpoint agent generating flow records. The pattern I use during hunts:

# In your SIEM (Elastic KQL syntax)
ssl.ja4 : "t13d1516h2_8daaf6152771_02713d6af862"
  AND NOT ssl.server_name : (*.microsoft.com OR *.googleapis.com)
  | stats count by source.ip, ssl.server_name, ssl.ja4s

Threat intelligence with the Intel framework

Zeek's Intel framework is a real-time in-memory matcher for indicators (IPs, domains, URLs, file hashes, certificate hashes, JA4 fingerprints). Feed it from MISP, OpenCTI, or a plain TSV and every event Zeek generates is checked against every indicator. No cron or batch lookup required.

# /opt/zeek/share/zeek/site/local.zeek additions
@load frameworks/intel/seen
@load frameworks/intel/do_notice
redef Intel::read_files += {
    "/opt/zeek/etc/intel/misp-domains.tsv",
    "/opt/zeek/etc/intel/misp-ips.tsv",
    "/opt/zeek/etc/intel/ja4-badclients.tsv"
};

An indicator file is TSV with a header row. Example:

#fields  indicator  indicator_type  meta.source  meta.desc
badc2.example  Intel::DOMAIN  misp:1234  Sliver C2 domain
203.0.113.9    Intel::ADDR    otx:88     Known infra
t13d1516h2_8daaf6152771_02713d6af862  Intel::JA4  redteam  Cobalt Strike default profile

Zeek reloads the files on modification, so a MISP-to-Zeek push script (there are several on GitHub) keeps your sensor current within seconds. Combine this with the Intel::seen events and you can trigger auto-response through your SOAR. My preferred pattern: Zeek fires Notice, Wazuh ingests it, and an active-response script updates the nftables ruleset described in the nftables hardening guide to drop the offending 5-tuple.

Cluster deployment for 10 Gbps and above

Zeek is single-threaded per worker. To scale beyond a couple of Gbps of real traffic you deploy a cluster: one manager, one logger, one or more proxies, and N workers, all coordinated by zeekctl. AF_PACKET fanout distributes flows across workers by 5-tuple hash so a given connection always lands on the same worker, essential for stateful analysis.

sudo -u zeek zeekctl deploy
sudo -u zeek zeekctl status

Name         Type    Host       Status    Pid
logger       logger  localhost  running   14322
manager      manager localhost  running   14361
proxy-1      proxy   localhost  running   14421
worker-1-1   worker  localhost  running   14567
worker-1-2   worker  localhost  running   14568
...

Sizing rule of thumb from Corelight's public guidance and my own benchmarks: one AF_PACKET worker per physical core, reserving 25 % of cores for the kernel, logger, and proxy. A dual-socket Xeon Gold with 32 physical cores comfortably handles a full 10 Gbps link running the default script set plus JA4 and Intel. If you see capture_loss above 0.1 % sustained, add workers or drop non-essential scripts (video streaming baselines are a common culprit) before you consider hardware capture cards.

SIEM integration: Wazuh, Elastic, and OpenSearch

Zeek writes JSON to /opt/zeek/logs/current/. Ship it with Filebeat, Vector, or Fluent Bit. The Elastic Zeek integration now normalises fields to ECS 8.x, so source.ip, destination.ip, network.community_id, and tls.client.ja4 all populate correctly out of the box.

# /etc/vector/vector.yaml
sources:
  zeek_logs:
    type: file
    include: ["/opt/zeek/logs/current/*.log"]
    read_from: end

transforms:
  parse_zeek:
    type: remap
    inputs: [zeek_logs]
    source: |
      . = parse_json!(.message)
      .event.dataset = "zeek." + string!(replace!(get_env_var!("VECTOR_FILE_NAME"), ".log", ""))
      .network.community_id = .community_id

sinks:
  opensearch:
    type: elasticsearch
    inputs: [parse_zeek]
    endpoints: ["https://opensearch.internal:9200"]
    api_version: v8
    mode: bulk
    bulk.index: "zeek-%Y.%m.%d"

For Wazuh, use the wodle-command to tail Zeek logs and forward as rule.group: zeek. Wazuh 5.x ships default correlation rules for Zeek notices, and its indexer clusters happily with the OpenSearch above, so you avoid running two search stores.

How attackers try to evade Zeek

Wearing my red team hat, here's what I actually do when I know Zeek is on the wire, and how you defend against each move:

  1. Blend TLS fingerprints. I switch my implant to use the platform TLS stack (SChannel on Windows, LibreSSL on macOS) so my JA4 matches every other host on the LAN. Defence: baseline JA4 per-host, not per-fleet. A workstation that suddenly speaks a Go TLS fingerprint is suspicious even if that fingerprint is common elsewhere.
  2. Domain fronting and ECH. The SNI leaks nothing useful. Defence: Zeek still sees the destination IP, certificate hash, and the connection shape (bytes-in vs bytes-out over time). A 30-second beacon interval with 5 KB requests is unmistakable in conn.log.
  3. Low and slow DNS tunnelling. One TXT lookup per minute stays under naive DGA detectors. Defence: the exfil.zeek policy plus per-host query-entropy baselining in the Intel framework catches subdomain entropy spikes.
  4. Kill the sensor. If I get onto the Zeek host itself, I stop zeekctl or fill the log disk. Defence: ship logs off-box within seconds (see the Vector config above), harden the sensor with the controls in my Ubuntu 24.04 hardening playbook, and monitor Zeek's own heartbeat with your SIEM.
  5. QUIC everywhere. Zeek's QUIC analyser has matured but coverage lags TCP. Defence: enable the quic and ech analysers from the community packages, and force enterprise Chrome/Edge to disable QUIC for outbound if your policy allows.

The takeaway? Zeek doesn't need to catch me on the first packet. It needs to be there for the second, third, and thousandth. The longitudinal metadata is what convicts a beacon, not any single alert. That's a fundamentally different game from signature IDS, and it's why NSM is having a renaissance in 2026 as encryption and cloud-native traffic patterns keep breaking the old signature-only model.

Frequently Asked Questions

Is Zeek free to use in commercial environments?

Yes. Zeek is released under a BSD 3-Clause license and is free for commercial use with no seat, throughput, or feature restrictions. Corelight sells a hardened enterprise distribution with support, connectors, and additional analytics, but the open-source Zeek from zeek.org is fully functional.

Can Zeek detect malware without signatures?

Yes, but not the way antivirus does. Zeek detects malware by behaviour: unusual JA4 fingerprints, new-domain-then-TLS patterns, DNS tunnelling entropy, files.log hashes matched against threat intel, and anomalous connection shapes. It complements a signature IDS rather than replacing one.

How does Zeek differ from Wireshark?

Wireshark is a packet analyser for interactive, per-packet inspection during troubleshooting. Zeek is a passive, headless monitor that reconstructs sessions and writes structured logs continuously across days or months of traffic. You use Wireshark to answer "what does this one flow look like"; you use Zeek to answer "what did every host on the network do last Tuesday".

What hardware do I need to run Zeek on a 10 Gbps link?

A dual-socket server with 32 physical cores, 128 GB RAM, and a Mellanox ConnectX-6 or Intel E810 NIC with RSS handles a saturated 10 Gbps link running the default policy plus JA4 and Intel framework. For 40 Gbps and above, deploy multiple sensors behind a network broker or use a hardware capture card such as Napatech.

Can Zeek block malicious traffic like Suricata IPS?

No. Zeek is strictly passive. To act on Zeek detections, forward notices to your SIEM or SOAR and trigger an active-response script that updates your firewall. For example, an nftables set on your edge router or an EDR quarantine on the endpoint.

Felix Lindqvist
About the Author Felix Lindqvist

Penetration tester and OSCP holder. Reverse engineers misconfigured servers for a living and writes about what he finds.