How to Build a Lightweight SOC Using Wazuh + Open Source

Why most small security programs fail before they start

"We need a SOC." It’s a sentence that gets said in every organization that has just experienced a breach, failed an audit, or hired a CISO for the first time. What usually follows is a commercial SIEM vendor pitch, a six-figure quote, and a 12-month deployment timeline.

Most teams walk away from that meeting and do nothing.

The irony is that a functional Security Operations Center — one that can detect real threats, alert on suspicious behavior, triage incidents, and satisfy most compliance requirements — can be built with open source tools for the cost of a few cloud VMs. The trade-off is time and expertise, not budget.

This guide walks through how to build a lightweight SOC stack anchored by Wazuh, what supporting tools to add and why, how to write detection rules that actually fire on real threats, and how to run it without burning out a small security team.


What a lightweight SOC actually needs to do

Before choosing any tool, it helps to define the minimum viable capability set. A lightweight SOC needs to cover four things:

Visibility: Know what is happening on your endpoints, network, and cloud workloads. Logs from everywhere, normalized into a format you can search and query.

Detection: Identify events that indicate something has gone wrong — unauthorized access, lateral movement, malware execution, data exfiltration, misconfiguration.

Alerting and triage: Surface the right alerts to the right person with enough context to make a decision. Not a flood of noise. Not silence.

Response: Take action. Block an IP. Isolate a host. Open a case. Document what happened.

Commercial SIEMs are sold on covering all four. Open source tools can cover all four too — they just require more intentional assembly.


The stack

Here is the minimal open source SOC stack this guide builds toward:

Layer Tool Role
SIEM / XDR Wazuh Log collection, rule-based detection, FIM, vulnerability scanning, active response
Network IDS Suricata Network-level threat detection, protocol analysis
Threat intelligence MISP IOC management, threat sharing
Incident response DFIR-IRIS Case management, investigation tracking, analyst workflow
Automation (optional) Shuffle SOAR-lite, webhook-based playbooks

This is not the only valid stack. But it is a coherent one — each tool has a clear lane, and they integrate cleanly with each other.


Component 1: Wazuh — the core of everything

Wazuh is the spine of this stack. It functions simultaneously as a SIEM, XDR, host-based intrusion detection system (HIDS), file integrity monitor (FIM), vulnerability scanner, and active response engine. That is a lot to ask of one platform — but it delivers on most of it, and the fact that it is free and open source makes it the obvious anchor for a resource-constrained SOC.

Architecture overview

flowchart TD
    subgraph Endpoints
        A1["Windows Agent"]
        A2["Linux Agent"]
        A3["macOS Agent"]
    end

    subgraph Network
        SUR["Suricata (IDS)\nNetwork traffic analysis"]
        FW["Firewall / VPN\nSyslog forwarding"]
    end

    subgraph Wazuh Core
        MGR["Wazuh Manager\nRule engine · Decoder pipeline · Active response"]
        IDX["Wazuh Indexer\n(OpenSearch)"]
        DASH["Wazuh Dashboard\nAlerts · FIM · Vuln · Compliance"]
    end

    subgraph Enrichment & Response
        MISP["MISP\nThreat intelligence · IOC feeds"]
        IRIS["DFIR-IRIS\nCase management · Investigation"]
        SHF["Shuffle (optional)\nSOAR playbooks · Auto-triage"]
    end

    A1 -->|"Encrypted log stream"| MGR
    A2 -->|"Encrypted log stream"| MGR
    A3 -->|"Encrypted log stream"| MGR
    SUR -->|"EVE JSON via syslog"| MGR
    FW -->|"Syslog (UDP/TCP 514)"| MGR

    MGR -->|"Indexed alerts"| IDX
    IDX --> DASH

    MGR -->|"IOC lookup"| MISP
    MGR -->|"Alert webhook"| SHF
    SHF -->|"Create case"| IRIS
    MGR -->|"Direct integration"| IRIS

What Wazuh gives you out of the box

When you deploy Wazuh and connect your first few agents, you get immediately:

  • Log collection and normalization from Windows Event Log, Linux syslog, macOS Unified Log, and dozens of application log formats
  • MITRE ATT&CK mapping — Wazuh’s default ruleset maps thousands of events to ATT&CK tactics and techniques, so your alerts speak the language your threat intel team and auditors already use
  • File Integrity Monitoring (FIM) — real-time alerting on changes to files, directories, permissions, and ownership on monitored paths
  • Security Configuration Assessment (SCA) — automated CIS benchmark scanning across your endpoints, with pass/fail results visible in the dashboard
  • Vulnerability detection — agent-based software inventory correlated against a continuously updated CVE database
  • Active response — configurable automated actions when rules trigger, including blocking IPs via firewalld or iptables, killing processes, or running custom scripts

Deploying Wazuh

The fastest path to a working Wazuh deployment is the all-in-one install script on a dedicated Ubuntu 22.04 or later server.

Minimum specs for a small deployment (up to ~50 agents):

  • 4 vCPU, 8 GB RAM, 100 GB SSD
  • Ubuntu 22.04 LTS
  • Open ports: 1514/UDP (agent communication), 1515/TCP (agent enrollment), 443/TCP (dashboard), 9200/TCP (indexer API, internal only)
# Download and run the Wazuh installation assistant
curl -sO https://packages.wazuh.com/4.x/wazuh-install.sh
bash wazuh-install.sh -a

This single command installs the Wazuh Manager, Indexer (OpenSearch), and Dashboard on the same host. For production environments with more than 50 agents, split these three components across separate servers.

After installation, retrieve your admin password:

tar -O -xvf wazuh-install-files.tar wazuh-install-files/wazuh-passwords.txt

Then navigate to https://YOUR_SERVER_IP and log in with admin and the retrieved password.

Enrolling agents

Linux:

curl -s https://packages.wazuh.com/key/GPG-KEY-WAZUH | gpg --dearmor \
  -o /usr/share/keyrings/wazuh.gpg
echo "deb [signed-by=/usr/share/keyrings/wazuh.gpg] \
  https://packages.wazuh.com/4.x/apt/ stable main" \
  | tee /etc/apt/sources.list.d/wazuh.list
apt update && apt install -y wazuh-agent
WAZUH_MANAGER="YOUR_MANAGER_IP" WAZUH_AGENT_NAME="web-01" \
  dpkg-reconfigure wazuh-agent
systemctl enable --now wazuh-agent

Windows (PowerShell):

Invoke-WebRequest -Uri https://packages.wazuh.com/4.x/windows/wazuh-agent-4.x.x-1.msi `
  -OutFile wazuh-agent.msi
msiexec /i wazuh-agent.msi /q `
  WAZUH_MANAGER="YOUR_MANAGER_IP" `
  WAZUH_AGENT_NAME="workstation-01"
NET START WazuhSvc

Component 2: Writing detection rules that matter

The default Wazuh ruleset is a solid starting point, but it casts a wide net. In practice, you will need to write custom rules to detect the specific threats that matter for your environment — and suppress the noise that doesn’t.

How Wazuh rules work

Wazuh processes every log event through a decoder pipeline first (which extracts structured fields from raw log text), then evaluates the decoded event against its rule tree. Rules are XML files stored in /var/ossec/etc/rules/. Custom rules go in local_rules.xml. Custom decoders go in /var/ossec/etc/decoders/local_decoder.xml.

Rules are evaluated hierarchically. A rule can reference a parent rule’s ID (if_sid), match on decoded fields (match, regex, field), count events within a time window (frequency, timeframe), or correlate across multiple rules (if_matched_sid).

Rule severity levels run from 0 to 15. Levels 1–3 are informational, 4–6 are low, 7–11 are medium, 12–15 are high/critical. Only levels 7 and above generate alerts in the default configuration.

Example 1: Detecting SSH brute force

This rule fires when the same source IP generates more than 5 failed SSH authentication attempts within 60 seconds:

<!-- /var/ossec/etc/rules/local_rules.xml -->
<group name="sshd,authentication_failed,">

  <!-- Parent rule: single SSH failure (already exists as rule 5716) -->
  <!-- We write a frequency-based child rule -->

  <rule id="100001" level="10" frequency="5" timeframe="60">
    <if_matched_sid>5716</if_matched_sid>
    <same_source_ip />
    <description>SSH brute force: more than 5 failed logins in 60s from $(srcip)</description>
    <mitre>
      <id>T1110.001</id>
    </mitre>
    <group>attack,brute_force,</group>
  </rule>

</group>

Example 2: Detecting suspicious PowerShell execution

This rule fires on PowerShell invocations that contain common obfuscation patterns — base64-encoded commands, download cradles, or execution policy bypasses:

<rule id="100010" level="12">
  <if_group>windows,sysmon,</if_group>
  <field name="win.eventdata.commandLine" \
    type="pcre2">(?i)(FromBase64String|EncodedCommand|DownloadString|DownloadFile|bypass|hidden|WebClient)</field>
  <description>Suspicious PowerShell execution: obfuscation or download cradle detected</description>
  <mitre>
    <id>T1059.001</id>
  </mitre>
  <group>attack,execution,powershell,</group>
</rule>

Note: This rule requires Sysmon installed on Windows endpoints and the Wazuh Sysmon ruleset loaded. Install Sysmon with the SwiftOnSecurity config for maximum coverage.

Example 3: Matching IOCs from a CDB list

Wazuh supports Constant Database (CDB) lists — fast-lookup key/value stores you can reference in rules. This pattern lets you match log events against a list of known malicious IPs, domains, or hashes:

# Create/update the IOC list
echo "185.220.101.45:" >> /var/ossec/etc/lists/malicious-ips
echo "194.165.16.11:" >> /var/ossec/etc/lists/malicious-ips

# Register the list in ossec.conf
# <ruleset>
#   <list>etc/lists/malicious-ips</list>
# </ruleset>
<rule id="100020" level="13">
  <if_group>firewall,</if_group>
  <list field="srcip" lookup="match_key">etc/lists/malicious-ips</list>
  <description>Connection from known malicious IP: $(srcip)</description>
  <mitre>
    <id>T1071</id>
  </mitre>
  <group>attack,threat_intel,</group>
</rule>

You can automate IOC ingestion from MISP into these CDB lists using a small Python script that polls the MISP API and rewrites the list file on a cron schedule.

Rule tuning: suppressing false positives

Every new Wazuh deployment produces a flood of alerts in the first week. Most of them are false positives — legitimate behavior that happens to match a detection rule. Suppress them with <overwrite> rules that reduce the severity, or with <list>-based whitelists.

<!-- Suppress failed login alerts from a known monitoring user -->
<rule id="100030" level="0" overwrite="yes">
  <if_sid>5716</if_sid>
  <user>monitoring-svc</user>
  <description>Suppress: expected failed login from monitoring service account</description>
</rule>

The discipline here is important: never suppress an entire rule class. Suppress specific, well-understood conditions. Keep a log of every suppression with the business justification. Audit suppressions quarterly.


Component 3: Suricata for network visibility

Wazuh agents give you excellent endpoint visibility. But lateral movement, command-and-control traffic, and data exfiltration often leave traces on the network that never touch an endpoint log. Suricata fills this gap.

Suricata is a high-performance network IDS/IPS that inspects traffic in real time using a signature-based detection engine (compatible with Snort rules), a protocol analyzer, and a file extraction engine. It outputs logs in EVE JSON format, which Wazuh ingests natively.

Installing Suricata

# Ubuntu 22.04
add-apt-repository ppa:oisf/suricata-stable -y
apt update && apt install -y suricata

# Download the latest Emerging Threats ruleset
suricata-update

Configuring Suricata to feed Wazuh

Edit /etc/suricata/suricata.yaml to set your monitored interface and enable EVE JSON output:

af-packet:
  - interface: eth0

outputs:
  - eve-log:
      enabled: yes
      filetype: regular
      filename: /var/log/suricata/eve.json
      types:
        - alert
        - http
        - dns
        - tls
        - flow

Then configure Wazuh to ingest the EVE log. On the Wazuh Manager, add to ossec.conf:

<localfile>
  <log_format>json</log_format>
  <location>/var/log/suricata/eve.json</location>
</localfile>

If Suricata runs on a separate sensor host, forward EVE JSON to the Wazuh Manager via syslog instead.

Wazuh ships with built-in decoders and rules for Suricata EVE JSON (rule group suricata), so alerts will appear in your dashboard immediately after ingestion with MITRE ATT&CK mappings where applicable.


Component 4: MISP for threat intelligence

A detection rule that matches a known-malicious IP or domain is orders of magnitude more actionable than a generic "anomalous connection" alert. MISP (Malware Information Sharing Platform) is the open source standard for managing and sharing threat intelligence — IOCs, threat actor profiles, malware samples, and incident data.

In a lightweight SOC, MISP serves two functions: it is the database where your threat analysts curate IOCs, and it is the feed that populates the CDB lists Wazuh uses for real-time matching.

Running MISP

MISP is most conveniently deployed via Docker:

git clone https://github.com/MISP/misp-docker
cd misp-docker
cp template.env .env
# Edit .env: set MISP_BASEURL, admin email, and password
docker compose up -d

Connecting free IOC feeds

MISP supports automatic sync from public threat feeds. Useful free feeds to enable in Settings → Feeds:

  • CIRCL OSINT Feed — general-purpose IOCs from Luxembourg’s national CERT
  • Abuse.ch URLhaus — active malware distribution URLs
  • Abuse.ch Feodo Tracker — botnet C2 IPs (Emotet, TrickBot, Qakbot)
  • AlienVault OTX — community-contributed IOCs (requires free API key)

Enable feeds and set them to auto-fetch every few hours. Your IOC database will grow continuously without manual curation.

Automating IOC → Wazuh CDB sync

#!/usr/bin/env python3
# misp_to_cdb.py — run every 30 minutes via cron
import requests, json, os

MISP_URL = "https://your-misp-instance"
MISP_KEY = "YOUR_API_KEY"
CDB_PATH = "/var/ossec/etc/lists/malicious-ips"

headers = {"Authorization": MISP_KEY, "Accept": "application/json",
           "Content-Type": "application/json"}

payload = {"returnFormat": "json", "type": "ip-dst",
           "tags": ["tlp:white", "tlp:green"], "limit": 5000}

r = requests.post(f"{MISP_URL}/attributes/restSearch",
                  headers=headers, json=payload, verify=False)
attrs = r.json().get("response", {}).get("Attribute", [])

with open(CDB_PATH, "w") as f:
    for attr in attrs:
        f.write(f"{attr['value']}:\n")

os.system("/var/ossec/bin/wazuh-logtest -U")  # reload CDB lists
print(f"Synced {len(attrs)} IOCs to {CDB_PATH}")

Component 5: DFIR-IRIS for case management

Wazuh generates alerts. MISP provides context. But neither tool answers the question: "Who is investigating this, what have they found, and what is the current status?" That is what DFIR-IRIS handles.

DFIR-IRIS is an open source collaborative incident response platform. It provides a case management system with timelines, evidence tracking, asset registers, IOC correlation, task assignment, and built-in integrations with MISP and Wazuh. When an alert in Wazuh escalates to an incident, a case is opened in IRIS. The analyst works the investigation there.

Running DFIR-IRIS

git clone https://github.com/dfir-iris/iris-web
cd iris-web
cp .env.model .env
# Edit .env: set IRIS_ADM_PASSWORD and IRIS_SECRET_KEY
docker compose up -d

Access the interface at http://localhost:4433. Default credentials: administrator / (the password you set in .env).

Connecting Wazuh alerts to IRIS cases

The cleanest integration path is through Shuffle (the SOAR layer described next). But you can also trigger IRIS case creation directly from Wazuh’s active response framework using a custom script:

#!/usr/bin/env python3
# iris_create_case.py — called by Wazuh active response on high-severity alerts
import sys, json, requests

IRIS_URL = "http://your-iris-instance:4433"
IRIS_API_KEY = "YOUR_IRIS_API_KEY"

alert = json.loads(sys.stdin.read())
rule_desc = alert.get("rule", {}).get("description", "Unknown")
agent_name = alert.get("agent", {}).get("name", "Unknown")
level = alert.get("rule", {}).get("level", 0)

if level < 12:
    sys.exit(0)  # only auto-create cases for high/critical alerts

payload = {
    "case_name": f"[AUTO] {rule_desc}",
    "case_description": json.dumps(alert, indent=2),
    "case_customer": 1,
    "case_soc_id": alert.get("id", ""),
    "custom_attributes": {}
}

r = requests.post(f"{IRIS_URL}/manage/cases/add",
                  headers={"Authorization": f"Bearer {IRIS_API_KEY}"},
                  json=payload)
print(r.json())

Component 6: Shuffle for lightweight automation

Shuffle is an open source SOAR platform that uses a visual workflow builder to connect your security tools via webhooks and APIs. In a lightweight SOC, it serves as the glue layer — receiving a Wazuh alert webhook, enriching it against MISP, creating an IRIS case, and notifying your analyst via Slack or email.

Deploying Shuffle

git clone https://github.com/Shuffle/Shuffle
cd Shuffle
docker compose up -d

Access at http://localhost:3001.

A basic triage workflow

The following workflow covers the most common pattern: a high-severity Wazuh alert triggers automatic enrichment and case creation.

flowchart LR
    W["Wazuh\nHigh-severity alert\n(level ≥ 12)"] -->|"Webhook POST\n/api/v1/hooks/..."| SHF

    subgraph Shuffle Workflow
        SHF["Trigger\nParse alert JSON"] --> ENR["Enrich\nLookup srcip in MISP API"]
        ENR --> DEC{"IOC\nmatched?"}
        DEC -->|"Yes"| HIGH["Set severity = CRITICAL\nAdd MISP context to case"]
        DEC -->|"No"| MED["Keep severity = HIGH"]
        HIGH --> CASE["IRIS\nCreate case with full context"]
        MED --> CASE
        CASE --> NOTIFY["Notify analyst\nSlack / Email / Teams"]
    end

To connect Wazuh to Shuffle, add the following to ossec.conf on your Wazuh Manager:

<integration>
  <name>shuffle</name>
  <hook_url>http://your-shuffle-instance:3001/api/v1/hooks/YOUR_HOOK_ID</hook_url>
  <level>12</level>
  <alert_format>json</alert_format>
</integration>

This sends every level 12+ alert to Shuffle as a JSON POST.


Operationalizing the SOC

Having the tools running is the easy part. Running the SOC effectively requires process, not just infrastructure.

Alert triage SLA

Define response time targets before you start. A simple three-tier model:

Severity Wazuh Level Target response Example
Critical 15 15 minutes Ransomware IOC matched, active exfiltration
High 12–14 2 hours Brute force success, privilege escalation
Medium 7–11 24 hours Repeated failed logins, suspicious process
Low 4–6 Weekly review Policy violations, informational

The daily analyst workflow

  1. Morning triage (30 min): Review overnight high/critical alerts in the Wazuh dashboard. For each: is it a true positive or false positive? True positives open a case in IRIS. False positives get a suppression rule added with documentation.

  2. IOC sweep (15 min): Check MISP for new feeds synced overnight. Review any new IOC matches in Wazuh. Update CDB lists if needed.

  3. Open case review (20 min): Review active IRIS cases. Update investigation notes, assign tasks, escalate or close.

  4. Rule maintenance (as needed): After any false positive identified in triage, write the suppression rule immediately. Do not let noise accumulate.

Metrics that matter

Track these weekly to know if your SOC is improving:

  • Mean Time to Detect (MTTD): Average time from event occurrence to alert generation. If this is rising, your log pipeline has a delay problem.
  • Mean Time to Respond (MTTR): Average time from alert to case closure. If this is rising, your team has a triage efficiency problem.
  • False positive rate: Number of alerts closed as false positives / total alerts. Target below 20%. If this is high, invest time in rule tuning.
  • Coverage gaps: Review MITRE ATT&CK Navigator monthly. Which tactics and techniques have no detection rules covering them? Prioritize writing rules for the most relevant gaps.

Handling alert fatigue

Alert fatigue is the silent killer of SOC programs. When analysts face hundreds of low-quality alerts per day, they start ignoring everything — including the real threats.

Defend against it:

  • Tune aggressively in week one. Spend the first week doing almost nothing but suppressing false positives. A quiet, high-precision alert stream is worth more than a comprehensive but noisy one.
  • Set alert volume targets. A team of one or two analysts can realistically triage 20–50 meaningful alerts per day. If you are generating 500, you have a tuning problem, not a capacity problem.
  • Use level thresholds. Only alert on levels 7 and above in real time. Levels 4–6 go into a queue for weekly review.
  • Review suppressions regularly. A suppression that was valid three months ago might now be hiding a real attack vector. Audit every suppression rule quarterly.

What this stack does not cover — and how to extend it

A lightweight SOC built on this stack will handle most threats that small to mid-sized organizations face. But there are gaps worth knowing about.

Cloud-native workloads: If your infrastructure runs heavily on AWS, GCP, or Azure, add Wazuh’s cloud module integrations. Wazuh can ingest CloudTrail, AWS GuardDuty findings, Azure Activity Logs, and GCP Audit Logs natively.

Container and Kubernetes visibility: Wazuh agents can run as DaemonSets in Kubernetes clusters. For deeper container runtime visibility, consider integrating Falco alongside Wazuh — Falco’s kernel-level syscall monitoring catches container escape attempts and unusual process trees that agent-based log collection misses.

User and Entity Behavior Analytics (UEBA): Wazuh’s rule engine is excellent at signature-based detection but has limited native support for anomaly-based detection. For behavioral baselines and anomaly scoring, consider adding OpenSearch’s Anomaly Detection plugin (it runs on the same OpenSearch instance that Wazuh already uses) or integrating a lightweight ML pipeline.

Log retention and compliance: Wazuh’s default retention is 90 days. Most compliance frameworks (PCI DSS, ISO 27001, PDPA in Thailand) require 12 months. Extend retention in your OpenSearch index lifecycle policy or archive cold logs to object storage (S3, GCS).


How Simplico builds this for clients

At Simplico, we deploy this stack (or variants of it) for clients who need functional security monitoring without the cost and complexity of commercial SIEM products. Our typical engagement covers:

Initial deployment and hardening: All-in-one or distributed Wazuh install, agent rollout across endpoints and servers, Suricata on network perimeter, MISP with initial feed configuration, DFIR-IRIS setup.

Detection engineering: Custom decoder and rule development for the client’s specific environment — their firewall vendor’s log format, their application stack, their ERP and ecommerce platform. We write rules, not just install them.

Tuning sprints: The first two weeks post-deployment are intensive tuning work. We track every false positive, write suppression rules, and bring alert volume down to a manageable level before handing off to the client’s team.

Runbook documentation: Every SOC is only as good as the humans running it. We deliver analyst runbooks for the most common alert types — what to check, how to triage, when to escalate, how to close.

If your team is evaluating whether to build a security monitoring capability and wants to avoid the common pitfalls, get in touch →


Wrapping up

Building a lightweight SOC with open source tools is entirely feasible. The components exist, they integrate cleanly, and the total infrastructure cost for a small organization is measured in tens of dollars per month, not hundreds of thousands.

The investment is in time and expertise. Wazuh takes an afternoon to install and a month to tune well. MISP requires ongoing feed curation. IRIS requires analysts who will actually use it. The tools do not run themselves.

But for organizations that make that investment, the result is a security monitoring capability that rivals commercial solutions in detection coverage, gives you complete visibility into the rule logic generating every alert, and can be extended and customized in ways that locked-down commercial SIEMs never allow.

The key decisions:

  • Start with Wazuh only. Get it tuned before adding more tools.
  • Add Suricata early — network visibility catches things endpoint agents miss.
  • Add MISP when you have someone to curate it. An IOC database nobody maintains is worse than none.
  • Add IRIS when you have enough alerts to warrant case tracking. Do not add it day one.
  • Add Shuffle when your triage workflow is stable enough to automate parts of it.

Build incrementally. Tune continuously. Document everything.


Simplico is a software engineering and product studio based in Bangkok, specializing in AI/RAG applications, security engineering, ecommerce integrations, and ERP connectivity across Thai, Japanese, and English-speaking markets. Learn more at simplico.net.


Get in Touch with us

Chat with Us on LINE

iiitum1984

Speak to Us or Whatsapp

(+66) 83001 0222

Related Posts

Our Products