Network Security
    IDS / NSM

    Deploy Suricata + Zeek on a VPS

    Run Suricata and Zeek side by side on a RamNode VPS for host-based network security monitoring — IDS rules, protocol logs, tuning, and shipping logs off-box.

    Suricata and Zeek (formerly Bro) are the two open-source pillars of network security monitoring. Suricata is a high-performance IDS/IPS engine with signature-based detection. Zeek is a network analysis framework that produces rich protocol logs and supports scripted detection logic. They are complementary, not competitive, and most mature SOCs run both. This guide walks through deploying them side by side on a RamNode KVM VPS to monitor the host's own traffic, ship logs off-box, and tune for the constraints of a VPS environment.

    Deployment Model and Sizing

    In a traditional enterprise deployment, Suricata and Zeek sit on a tap or span port and see all traffic from a network segment. On a cloud VPS, that model does not apply. What you get instead is host-based network monitoring: the sensor watches the traffic in and out of the VPS itself, on its primary network interface. This is genuinely useful for monitoring exposed services, detecting lateral movement attempts against your VPS, and producing forensic logs of inbound and outbound flows.

    Plan the following on RamNode:

    • 4 GB RAM minimum, 8 GB recommended once you load full Emerging Threats Pro or commercial rules
    • 4 vCPU minimum; both engines parallelize across cores
    • 80 GB disk minimum if you retain logs locally; budget more if you index with Elasticsearch on-box
    • Ubuntu 24.04 LTS

    If you intend to ship logs to a remote SIEM, you can run on a smaller plan since you are not retaining the corpus locally. If you intend to index logs on the same host with Elasticsearch or OpenSearch, you need a much larger plan and should consider whether a separate analytics VPS makes more sense.

    Initial Setup

    Update the base system and install supporting tools:

    shell
    apt update && apt -y full-upgrade
    apt -y install ufw jq curl gnupg2 software-properties-common chrony
    systemctl enable --now chrony

    Accurate time is non-negotiable for network monitoring. Confirm:

    shell
    chronyc tracking

    Configure UFW to permit only what the host needs. Note that neither Suricata nor Zeek requires inbound ports; they read traffic off the interface in promiscuous mode (which on a KVM VPS is more accurately "all traffic destined for this VM's MAC").

    shell
    ufw default deny incoming
    ufw default allow outgoing
    ufw allow 22/tcp
    ufw --force enable

    Installing Suricata

    The OISF maintains a current PPA for Ubuntu. Use it rather than the distro package, which tends to lag by major versions:

    shell
    add-apt-repository -y ppa:oisf/suricata-stable
    apt update
    apt -y install suricata jq

    Identify the network interface Suricata should monitor:

    shell
    ip -o -4 addr show | awk '{print $2": "$4}'

    You are looking for the interface with the VPS's public IPv4. On RamNode KVM, this is typically eth0 or ens3. Edit /etc/suricata/suricata.yaml and set the AF_PACKET interface accordingly:

    shell
    af-packet:
      - interface: ens3
        threads: auto
        cluster-id: 99
        cluster-type: cluster_flow
        defrag: yes
        use-mmap: yes
        tpacket-v3: yes
        ring-size: 200000
        block-size: 1048576

    Set the home network. For a VPS, the home network is just the host itself:

    shell
    vars:
      address-groups:
        HOME_NET: "[YOUR.VPS.PUB.IP/32]"
        EXTERNAL_NET: "!$HOME_NET"

    Substitute your actual public IP. If you have multiple IPs assigned to the VPS, include all of them.

    Suricata Rules Management

    The signature corpus is what makes Suricata useful. The standard tool is suricata-update, which is bundled with the PPA package:

    shell
    suricata-update update-sources
    suricata-update enable-source et/open
    suricata-update enable-source oisf/trafficid
    suricata-update enable-source ptresearch/attackdetection
    suricata-update

    Validate the config and rules before restarting:

    shell
    suricata -T -c /etc/suricata/suricata.yaml -v

    If the test passes, enable and start the service:

    shell
    systemctl enable --now suricata
    systemctl status suricata

    Schedule daily rule updates via cron. Edit root's crontab:

    shell
    15 3 * * * /usr/bin/suricata-update --quiet && /usr/bin/systemctl kill -s USR2 suricata

    The USR2 signal triggers a live rule reload without dropping packets.

    Suricata Performance Tuning

    The two settings that matter most on a VPS are thread count and flow timeouts. For thread count, threads: auto will use one worker per available core. On a 4 vCPU plan that is correct. If you start dropping packets, check:

    shell
    jq '.suricata.capture.kernel_drops' /var/log/suricata/stats.log

    A non-zero kernel_drops count means the kernel is dropping packets before Suricata sees them. Fix this by increasing the ring-size or the af-packet block-size, in that order.

    Flow timeouts default to values appropriate for a corporate LAN, not a public-facing VPS that sees TCP scan noise constantly. In /etc/suricata/suricata.yaml:

    shell
    flow-timeouts:
      default:
        new: 10
        established: 100
        closed: 5
        bypassed: 50
        emergency-new: 5
        emergency-established: 50
        emergency-closed: 2
        emergency-bypassed: 25
      tcp:
        new: 30
        established: 300
        closed: 10

    Tighter timeouts mean lower memory pressure from the flow table at the cost of merging some long-lived flows.

    Installing Zeek

    Zeek ships current packages via the OBS repository:

    shell
    echo 'deb http://download.opensuse.org/repositories/security:/zeek/xUbuntu_24.04/ /' \
      > /etc/apt/sources.list.d/security:zeek.list
    curl -fsSL https://download.opensuse.org/repositories/security:zeek/xUbuntu_24.04/Release.key \
      | gpg --dearmor > /etc/apt/trusted.gpg.d/security_zeek.gpg
    apt update
    apt -y install zeek

    Add Zeek to PATH for all users:

    shell
    echo 'export PATH=/opt/zeek/bin:$PATH' > /etc/profile.d/zeek.sh
    source /etc/profile.d/zeek.sh

    Configure the node. Edit /opt/zeek/etc/node.cfg:

    shell
    [zeek]
    type=standalone
    host=localhost
    interface=ens3

    Edit /opt/zeek/etc/networks.cfg to define the local network. On a VPS this is just the host:

    shell
    YOUR.VPS.PUB.IP/32  VPS host

    Edit /opt/zeek/etc/zeekctl.cfg for log management:

    shell
    LogRotationInterval = 3600
    LogExpireInterval = 7day
    MailTo =
    MailConnectionSummary = 0

    The empty MailTo and disabled connection summary email are deliberate. RamNode does not allow mail services, so Zeek's email reporting is non-functional. Use log shipping for alerts instead.

    Running Zeek Alongside Suricata

    Both engines need to read from the same interface. AF_PACKET cluster mode allows this without contention. Suricata already uses cluster-flow mode; Zeek's default uses pcap directly, which is fine for a single-process deployment but performs poorly under load. For a single-VPS deployment with modest traffic this is acceptable.

    Validate and deploy Zeek:

    shell
    zeekctl deploy
    zeekctl status

    Logs land in /opt/zeek/logs/current/. The key log files are conn.log (connection summaries), dns.log, http.log, ssl.log, notice.log, and weird.log. The richness of these logs versus what Suricata produces is the whole reason to run Zeek.

    Zeek Scripting and Detection Packages

    Zeek's real power is its scripting language. The community maintains detection packages installable via zkg:

    shell
    zkg autoconfig
    zkg install zeek/cybera/zeek-sniffpass
    zkg install zeek/corelight/zeek-community-id
    zkg install zeek/salesforce/ja3

    The Community ID plugin produces a flow hash that correlates Zeek and Suricata events for the same connection, which is essential if you ship both into a SIEM. JA3 produces TLS client fingerprints that are useful for malware identification.

    After installing packages, reload Zeek:

    shell
    zeekctl deploy

    Log Shipping

    Local logs are a starting point, not a destination. Ship them to a central platform. Vector is the cleanest agent for this; Filebeat is the traditional choice if you are running Elastic. A minimal Vector config for both engines at /etc/vector/vector.yaml:

    shell
    sources:
      suricata_eve:
        type: file
        include:
          - /var/log/suricata/eve.json
        read_from: end
    
      zeek_logs:
        type: file
        include:
          - /opt/zeek/logs/current/*.log
        read_from: end
    
    transforms:
      suricata_parse:
        type: remap
        inputs: [suricata_eve]
        source: |
          . = parse_json!(.message)
          .source_type = "suricata"
          .host = get_hostname!()
    
      zeek_parse:
        type: remap
        inputs: [zeek_logs]
        source: |
          .source_type = "zeek"
          .host = get_hostname!()
    
    sinks:
      remote_siem:
        type: http
        inputs: [suricata_parse, zeek_parse]
        uri: https://siem.example.org/ingest
        encoding:
          codec: json
        auth:
          strategy: bearer
          token: "${SIEM_TOKEN}"

    Install Vector:

    shell
    bash -c "$(curl -L https://setup.vector.dev)"
    apt install -y vector
    systemctl enable --now vector

    Log Rotation and Disk Management

    Even with shipping enabled, the local logs need rotation. Suricata uses its built-in rotation; verify in suricata.yaml that outputs.eve-log.filetype: regular is set and that you have a logrotate entry:

    /etc/logrotate.d/suricata:

    shell
    /var/log/suricata/*.log /var/log/suricata/*.json {
      daily
      rotate 14
      compress
      delaycompress
      missingok
      notifempty
      postrotate
        /bin/kill -HUP $(cat /var/run/suricata.pid 2>/dev/null) 2>/dev/null || true
      endscript
    }

    Zeek rotates internally based on LogRotationInterval in zeekctl.cfg, and LogExpireInterval controls retention of compressed archives. Confirm /opt/zeek/logs/ is being pruned by checking dated subdirectories.

    Operational Validation

    Confirm both engines are seeing traffic. For Suricata:

    shell
    tail -f /var/log/suricata/eve.json | jq .

    You should see flow events accumulating. Trigger an alert deliberately for validation:

    shell
    curl http://testmynids.org/uid/index.html

    The Emerging Threats GPL test rule should fire and produce an alert event in eve.json within seconds.

    For Zeek:

    shell
    tail -f /opt/zeek/logs/current/conn.log

    The Zeek logs are tab-separated by default. Pipe through zeek-cut for readability:

    shell
    zeek-cut id.orig_h id.resp_h id.resp_p service < /opt/zeek/logs/current/conn.log | head

    Monitoring the Monitors

    Both engines need their own monitoring. Watch for:

    • Suricata kernel_drops increasing (indicates packet loss)
    • Suricata memuse approaching the configured limit
    • Zeek capture_loss notices in notice.log
    • Disk usage on /var/log/suricata and /opt/zeek/logs
    • Both services running under systemd

    A simple health check script for cron:

    shell
    #!/usr/bin/env bash
    systemctl is-active --quiet suricata || echo "ALERT: suricata down"
    pgrep -x zeek > /dev/null || echo "ALERT: zeek down"
    DROPS=$(jq -r '.suricata.capture.kernel_drops' /var/log/suricata/stats.log 2>/dev/null | tail -1)
    [ "${DROPS:-0}" -gt 1000 ] && echo "ALERT: suricata kernel drops at $DROPS"

    The natural next step is detection engineering: writing custom Suricata rules for your environment and Zeek scripts that codify your threat model. After that, the integration layer (correlating Suricata alerts to Zeek protocol context via Community ID, then enriching with MISP threat intel) is what turns raw events into actionable signal. Each of those deserves its own guide.