← All Guides
advanced

Wazuh in Your Homelab: A Real SIEM Without the Enterprise Price Tag

Deploy Wazuh as a dedicated Proxmox VM to get centralized security monitoring, file integrity alerts, and vulnerability scanning across your entire homelab, with agents on every Linux host and Windows coverage via Tailscale.

Budget Homelab ·
networkingsecurityproxmoxmonitoring

Most homelab setups have zero security monitoring. Not because people don’t care — because it never feels urgent until it is. You’re running Authelia, your services are behind NPM, you’re not opening ports directly. You feel pretty good about it.

Then you start wondering: would I actually know if someone was brute-forcing Authelia? Would I notice if someone modified my SSH authorized_keys? If a container started making weird outbound connections at 3am, would anything tell me?

Probably not.

That’s the gap Wazuh fills. It’s an open-source SIEM and XDR platform — the same category of tool enterprises pay six figures a year for — and it runs fine on a single VM in your homelab. I set it up across 18 hosts (VMs, LXCs, the Proxmox hypervisor itself, and one external Windows machine via Tailscale) and it’s become the part of my stack I’m most glad I built.

This guide covers the full deployment: server on Proxmox, agent rollout, Docker container monitoring, alert tuning, and extending coverage to external Windows machines.

What Wazuh Actually Does

Four core components, all running on one VM for a homelab deployment:

Wazuh Manager — the brain. Receives data from agents, runs detection rules, triggers alerts. Listens on ports 1514 (agent events) and 1515 (agent enrollment).

Wazuh Indexer — an OpenSearch fork (itself an Elasticsearch fork). Stores all alerts and events in searchable indices. This is the memory hog. It wants heap, and it will take whatever you give it.

Wazuh Dashboard — a web UI built on OpenSearch Dashboards. This is where you spend your time reviewing alerts, managing agents, and building visualizations. Runs on port 443.

Wazuh Agent — a lightweight daemon installed on each monitored host. Collects logs, does file integrity monitoring, runs vulnerability scans, and reports everything back to the manager. Typically 40-60 MB RAM per agent. Negligible CPU.

The flow is simple: agents send events to the manager, the manager analyzes them and writes alerts to the indexer, the dashboard reads from the indexer. Everything runs on one VM. That’s the right architecture for 15-25 endpoints.

The VM: Why Dedicated, Why Not Docker

Before getting into installation, the deployment decision: this should be a dedicated VM on Proxmox, not Docker on your existing Docker host, and not an LXC.

Not Docker on a shared host. Wazuh’s stack (manager + indexer + dashboard) consumes 6-8 GB RAM at idle. If your Docker host is already running Authelia, Vaultwarden, Plausible, Portainer, and a handful of other stacks, adding Wazuh means you’re also adding OpenSearch’s JVM heap to a machine that’s already busy. The Wazuh indexer and ClickHouse (if you run Plausible) do not coexist gracefully under memory pressure. One of them will OOM.

Not an LXC. The Wazuh manager uses kernel-level features — the audit subsystem, seccomp, syscall monitoring. LXCs share the PVE host kernel, which creates mismatches that aren’t well-documented and aren’t worth debugging. The Proxmox community helper scripts include an LXC option, but reports of OpenSearch instability inside unprivileged LXCs are common. Use a VM.

A dedicated VM gives you a clean security appliance: its own kernel, its own disk, isolated from everything else. If Wazuh crashes (it won’t once tuned, but still), it doesn’t take anything with it.

VM Specs and Installation

Provision a new VM on your Proxmox node with the most available RAM. Ubuntu 24.04 LTS is the right OS — consistent with other Ubuntu hosts and well-supported by Wazuh.

VM specs:

ResourceAllocation
CPU4 vCPU
RAM12 GB
Disk100 GB
OSUbuntu 24.04 LTS
IPStatic (e.g., 192.168.1.80)

The 12 GB RAM is non-negotiable. The indexer defaults to 50% of system RAM for JVM heap. On an 8 GB VM, that’s 4 GB for the indexer, leaving 4 GB for the OS, manager, and dashboard. The system will be sluggish, events will drop, and you’ll chase intermittent crashes. 12 GB gives you comfortable headroom.

After provisioning, set a static IP via netplan or DHCP reservation, then run the all-in-one installer:

curl -sO https://packages.wazuh.com/4.9/wazuh-install.sh
sudo bash ./wazuh-install.sh -a

The -a flag installs everything: manager, indexer, dashboard, and the Filebeat connector between them. It takes 10-15 minutes. At the end, the installer prints admin credentials — save them to your password manager immediately. You won’t see them again without digging into config files.

Once the installer finishes, the dashboard is reachable at https://192.168.1.80 (your Wazuh VM IP). It uses a self-signed cert by default. If you have a domain and NPM, add a proxy host pointing to the Wazuh VM’s port 443, with SSL verification disabled on the NPM side (self-signed is fine for LAN-to-LAN). Put Authelia in front of it — belt and suspenders on a security dashboard is appropriate.

First Performance Tuning (Do This Before Anything Else)

Before you enroll a single agent, tune the indexer’s JVM heap. The default of 50% of RAM is too aggressive for a homelab single-node deployment:

sudo nano /etc/wazuh-indexer/jvm.options

Find the -Xms and -Xmx lines and set them to 4 GB:

-Xms4g
-Xmx4g

On a 12 GB VM, this leaves 8 GB for the OS, manager, and dashboard. Restart the indexer:

sudo systemctl restart wazuh-indexer

Also set up index lifecycle policies before the data starts rolling in. Without them, indices grow forever. Log in to the dashboard, go to Index Management, and create an ISM policy:

This takes 10 minutes and saves you from finding a full disk two months later.

Agent Rollout: High-Value Hosts First

Agent enrollment is straightforward. On each Linux host:

# Add Wazuh repo
curl -s https://packages.wazuh.com/key/GPG-KEY-WAZUH | sudo apt-key add -
echo "deb https://packages.wazuh.com/4.x/apt/ stable main" | sudo tee /etc/apt/sources.list.d/wazuh.list
sudo apt update

# Install and enroll in one step — replace 192.168.1.80 with your Wazuh VM IP
sudo WAZUH_MANAGER="192.168.1.80" WAZUH_AGENT_NAME="$(hostname)" apt install wazuh-agent -y
sudo systemctl daemon-reload
sudo systemctl enable --now wazuh-agent

The agent auto-enrolls with the manager on first connection. No manual key exchange. Within a couple minutes, the agent shows up in the dashboard under Agents.

Deploy in priority order based on what’s most sensitive:

Phase 1 — highest value:

Phase 2 — second tier:

Each agent costs roughly 50-100 MB RAM on the host and under 1% CPU. At 18 agents the aggregate is negligible. The server VM is the only meaningful resource commitment.

One gotcha for the Proxmox hypervisor: the Wazuh agent’s rootcheck module may flag Proxmox kernel modules as suspicious. False positives — add exclusions to /var/ossec/etc/ossec.conf on that agent:

<rootcheck>
  <skip_nfs>yes</skip_nfs>
  <ignore>/sys/kernel/security</ignore>
  <ignore>/proc/</ignore>
</rootcheck>

Monitoring Docker Containers via the Host Agent

For a host running Docker, you don’t install agents inside individual containers. The agent on the Docker host monitors everything via two methods.

Docker listener module — tracks container lifecycle events (start, stop, restart, exec, image pulls, volume changes). Add this to /var/ossec/etc/ossec.conf on the Docker host agent:

<wodle name="docker-listener">
  <disabled>no</disabled>
  <attempts>5</attempts>
  <run_on_start>yes</run_on_start>
  <interval>10m</interval>
</wodle>

This tells you when someone runs docker exec into a container, when images get pulled unexpectedly, when containers restart repeatedly.

Container log monitoring — for app-level visibility (Authelia auth failures, Vaultwarden access logs), configure the agent to read Docker’s JSON log files directly. In the same ossec.conf:

<localfile>
  <log_format>json</log_format>
  <location>/var/lib/docker/containers/*/*.log</location>
  <label key="docker.container">/var/lib/docker/containers/%{filename}</label>
</localfile>

One caveat: chatty containers in debug mode can flood the indexer. If a container is writing thousands of lines per minute, add an <exclude> pattern for its log path, or configure Docker’s log driver to cap file size. In your Docker Compose config or daemon.json:

{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  }
}

For Unraid (Slackware-based, no standard package manager), skip the agent. Use syslog forwarding instead. In Unraid’s settings, point the remote syslog to your Wazuh VM IP on UDP 514. You lose FIM and vulnerability scanning, but you get auth events, Docker events, and disk health logs. Sufficient for a NAS.

Alert Tuning: Surviving the First Two Weeks

Out of the box, Wazuh generates thousands of low-severity alerts per day. This is not a sign something is wrong — it’s the default sensitivity being calibrated for enterprise environments, not a homelab.

Do not try to interpret all of this noise. Instead, start by raising the alert display threshold to level 7. In the dashboard, filter alerts to show only severity 7 and above. Everything below that you will review manually over the first week and suppress what’s pure noise.

The most common culprits:

Rule 5715 (sshd session opened/closed) — fires on every SSH connection. On any system with regular SSH activity, this generates hundreds of alerts per day. Suppress it.

Rule 550 (log file rotated) — fires every time logrotate runs. Informational, not actionable. Suppress it.

Rule 503 (agent started) — fires every time an agent starts or reconnects. Useful for detecting agent tampering in theory; in practice it fires on every server reboot. Keep it at a low level where it doesn’t dominate your feed.

SCA check failures — out of the box, SCA runs CIS benchmarks that flag things like “IPv6 not disabled” and “USB storage not blocked.” These are irrelevant for most homelab setups. Tune the SCA policy to match your actual security posture, not a generic benchmark.

To suppress a rule, add a local rules override file at /var/ossec/etc/rules/local_rules.xml:

<group name="local_overrides,">
  <!-- Suppress sshd session noise -->
  <rule id="100001" level="0">
    <if_sid>5715</if_sid>
    <description>Suppress: sshd session opened/closed</description>
  </rule>

  <!-- Suppress log rotation -->
  <rule id="100002" level="0">
    <if_sid>550</if_sid>
    <description>Suppress: logrotate</description>
  </rule>
</group>

After two weeks of this, you’ll have a feed where most alerts are actually worth looking at: file changes in /etc/ssh/, failed Authelia logins, new processes appearing on hosts where the process list should be stable, unexpected outbound connections.

Custom Decoders for Your Stack

Wazuh has built-in decoders for nginx, sshd, Docker, and hundreds of other services. But for homelab-specific services like Authelia, you may want to write a short custom decoder to extract structured fields from JSON logs.

Create /var/ossec/etc/decoders/local_decoders.xml:

<decoder name="authelia">
  <prematch>{"level"</prematch>
  <type>json</type>
  <fields>
    <field name="level">$.level</field>
    <field name="method">$.method</field>
    <field name="path">$.path</field>
    <field name="remote_ip">$.remote_ip</field>
    <field name="username">$.username</field>
    <field name="message">$.message</field>
  </fields>
</decoder>

Then add a corresponding rule that fires on failed authentication patterns in those fields. Once this is in place, Wazuh can alert specifically on repeated Authelia auth failures from a single IP — and with active response configured, it can automatically block that IP at the firewall.

For NPM (nginx-based), the built-in nginx decoder already handles most log patterns. Rules 31100-31199 cover brute force detection, unusual HTTP methods, and common web attack signatures against proxied services. You get this for free just by having an agent on the NPM LXC reading the access logs at /data/logs/.

File Integrity Monitoring for the Things That Matter

FIM is where Wazuh earns its keep in a homelab. Configure it to watch the directories that, if changed, mean something went wrong.

In the agent’s ossec.conf on hosts with sensitive files:

<syscheck>
  <frequency>43200</frequency> <!-- 12-hour scan interval, reasonable for homelab -->

  <!-- SSH keys and configs -->
  <directories check_all="yes" realtime="yes">/etc/ssh</directories>
  <directories check_all="yes" realtime="yes">/root/.ssh</directories>

  <!-- System binaries -->
  <directories check_all="yes">/usr/bin</directories>
  <directories check_all="yes">/usr/sbin</directories>
  <directories check_all="yes">/bin</directories>

  <!-- App configs on Authelia/Docker host -->
  <directories check_all="yes" realtime="yes">/opt/authelia</directories>

  <!-- Exclude volatile paths that generate false positives -->
  <ignore>/proc</ignore>
  <ignore>/sys</ignore>
  <ignore>/run</ignore>
  <ignore>/tmp</ignore>
</syscheck>

The realtime="yes" attribute on the most sensitive directories triggers alerts immediately on change rather than waiting for the next scheduled scan. Use it on SSH keys, auth configs, and anything else where you want to know right now, not 12 hours from now.

External Windows Machines via Tailscale

If your homelab subnet is already advertised via a Tailscale subnet router, extending Wazuh monitoring to external Windows machines costs you nothing on the network side. Any machine on your tailnet can reach the Wazuh VM as if it were on the LAN.

First, make sure your Wazuh VM’s firewall allows agent connections from the Tailscale CGNAT range:

sudo ufw allow from 10.0.0.0/8 to any port 1514 proto tcp
sudo ufw allow from 10.0.0.0/8 to any port 1515 proto tcp
sudo ufw allow from 100.64.0.0/10 to any port 1514 proto tcp
sudo ufw allow from 100.64.0.0/10 to any port 1515 proto tcp

On the Windows machine, install Tailscale, confirm it can reach your Wazuh VM IP, then download the Wazuh Windows MSI from packages.wazuh.com (match the exact version to your deployed manager). Install via PowerShell as administrator:

msiexec.exe /i wazuh-agent-4.9.x-1.msi `
  WAZUH_MANAGER="192.168.1.80" `
  WAZUH_AGENT_NAME="family-pc" `
  WAZUH_REGISTRATION_SERVER="192.168.1.80" /q

NET START WazuhSvc

After enrollment, create an agent group in the dashboard called external-windows and assign the machine to it. Apply a conservative group policy — Windows Event Logs (Application, System, Security), FIM on the user profile and System32, but no vulnerability scanning initially. Vulnerability scanning on consumer Windows machines generates noise from outdated software that isn’t actionable.

One important tuning step for home PCs: they get turned off. The default Wazuh disconnect alert threshold is 10 minutes, which means every time someone closes the laptop lid you get an alert. In /var/ossec/etc/ossec.conf on the Wazuh manager, under <ossec_config>:

<global>
  <agents_disconnection_time>600</agents_disconnection_time>
  <agents_disconnection_alert_time>3600</agents_disconnection_alert_time>
</global>

Mark it disconnected after 10 minutes, but don’t fire an alert until it’s been gone for an hour. Restart the manager after: sudo systemctl restart wazuh-manager.

What you get from a Windows agent: failed login tracking, malware indicators via FIM and event logs, Windows Update status, software inventory, unexpected process creation. If ransomware starts encrypting files or a trojan modifies system binaries, you’ll see it. If someone tries to log in while your parents are at dinner, you’ll see it.

What This Looks Like in Practice

After the first two weeks of tuning, the dashboard is a place you check the same way you check Uptime Kuma — quick scan, nothing unusual, move on. The alerts that do fire are the ones worth reading.

What actually surfaces in a typical homelab:

None of these are necessarily a breach. But they’re the signal that tells you to look closer. Before Wazuh, you didn’t have any of that signal. You had services up and services down.

The install takes a couple hours. The tuning takes a couple weeks. The payoff is knowing your homelab the way someone who actually watches it knows it — not just that everything’s up, but whether anything’s wrong.

Quick Reference: Ports and Services

PortProtocolPurpose
1514TCPAgent event forwarding (encrypted)
1515TCPAgent auto-enrollment
55000TCPWazuh REST API
443TCPDashboard (HTTPS)
514UDPSyslog input (agentless devices)
9200TCPIndexer API (internal only)

All agents connect outbound to the manager. No inbound ports needed on agents. The dashboard port is the only one that needs to be accessible from your browser, and that goes through NPM if you’re following the standard homelab pattern.