How to Install Wazuh on Ubuntu 24.04 VPS: Open-Source SIEM & XDR for Self-Hosted Security
If your security stack currently stops at fail2ban and a few grep pipelines on /var/log/auth.log, you are only seeing a fraction of what happens on your servers. A proper Security Information and Event Management (SIEM) platform correlates logs across every host, flags anomalies against rule libraries, maps detections to MITRE ATT&CK tactics, and triggers automated response actions — all from a single dashboard. Wazuh is the leading open-source SIEM/XDR and the most practical way to get enterprise-grade detection without an enterprise-grade invoice.
This guide walks you through installing Wazuh 4.x on a single Ubuntu 24.04 LTS VPS, enrolling Linux and Windows agents, enabling file integrity monitoring, vulnerability detection, and MITRE ATT&CK rule mappings, wiring up integrations with Slack, VirusTotal, and Shuffle SOAR, publishing the dashboard behind Nginx with TLS, and setting up a solid backup routine.
Want the managed platform instead? Deploy a hardened Ubuntu 24.04 VPS sized for SIEM workloads in a couple of clicks. CloudCore Business on vps-server.host gives you the CPU, RAM, and NVMe headroom Wazuh's indexer actually needs.
Table of Contents
What is Wazuh?
Wazuh is an open-source security platform originally forked from OSSEC that has grown into a full SIEM and XDR. It ingests logs from agents and cloud APIs, applies thousands of correlation rules, stores events in a Wazuh Indexer (an OpenSearch fork), and surfaces everything through a Kibana-style dashboard. The full platform is licensed under AGPLv2 with no agent limits, no ingest caps, and no paid tier — what you self-host is what paying Wazuh Cloud customers get.
A Wazuh deployment has three server-side components:
- Wazuh manager — Receives events from agents, applies decoders and rules, stores alerts, triggers active responses, and exposes the management API.
- Wazuh indexer — OpenSearch-based search and analytics engine that stores alerts, file integrity events, vulnerability data, and raw events with full-text search.
- Wazuh dashboard — Web UI for querying data, building visualizations, managing agents, and operating integrations.
Typical use cases include regulated-industry compliance (PCI DSS, HIPAA, GDPR, NIST 800-53, TSC), MITRE ATT&CK-aligned threat hunting, container and Kubernetes runtime security, cloud workload protection for AWS, Azure, GCP, and GitHub audit logs, and replacing point products like OSSEC, Tripwire, or commercial EDR.
Why Self-Host a SIEM Instead of Paying for Splunk or Elastic Cloud?
Running your own Wazuh cluster instead of buying Splunk Cloud, Elastic Cloud, Datadog Cloud SIEM, or Microsoft Sentinel comes with concrete advantages:
- Flat, predictable cost — A CloudCore Business VPS is a fixed monthly price regardless of ingest volume. Splunk Cloud starts at roughly $1,800/mo for 5 GB/day and scales to five-figure monthly bills fast. Elastic Cloud and Datadog meter both ingest and retention.
- No per-agent licensing — Wazuh supports unlimited agents. Microsoft Sentinel, SentinelOne, and CrowdStrike all charge per endpoint per month.
- Full data sovereignty — Every byte of telemetry stays on infrastructure you control. This matters for GDPR, SOC 2, regulated finance, healthcare, and defense workloads where shipping raw logs to a US cloud vendor is a non-starter.
- Unlimited retention — Cloud SIEMs charge premium rates for data older than 30 days. On your own VPS with NVMe storage you decide how long you keep data.
- Hack-friendly — You own the rule files, the decoders, the API. You can fork them, write custom decoders for proprietary apps, and contribute upstream.
- No vendor lock-in — Alerts live in an OpenSearch index and can be reindexed, exported, or migrated without vendor cooperation.
Cost Comparison: Wazuh Self-Hosted vs. Managed SIEMs
| Scenario (10 GB/day ingest, 50 endpoints, 30-day retention) | Splunk Cloud | Elastic Cloud (Security) | Microsoft Sentinel | Self-Hosted Wazuh (VPS) |
|---|---|---|---|---|
| Typical monthly cost | $3,000 – $6,000 | $1,200 – $2,500 | $2,300 – $4,000 | EUR 29.99/mo (flat) |
| Per-agent licensing | Bundled | Bundled | Yes (per node) | None |
| Retention beyond 30 days | Paid tier | Paid tier | Paid tier | Unlimited (disk-bound) |
| Data leaves your infrastructure? | Yes | Yes | Yes | No |
| MITRE ATT&CK mapping | Yes | Yes | Yes | Yes |
| Open-source rules | No | Partially | No | Yes |
| Custom decoders | Via SPL | Limited | Limited | Unlimited |
Prerequisites
Before you begin, make sure you have:
- A VPS running Ubuntu 24.04 LTS with root or sudo access and at least 50 GB of NVMe storage.
- SSH access to the server.
- At least 8 vCPU and 16 GB of RAM for the all-in-one install with 25–100 agents. For 150+ agents, split components across two or three nodes.
- A public DNS record such as
wazuh.yourdomain.compointing at the VPS (for TLS on the dashboard). - Ports open:
1514/tcp+udp(agent events),1515/tcp(agent enrollment),55000/tcp(manager API),9200/tcp(indexer, localhost only),443/tcp(dashboard — via Nginx).
Recommended Plan: CloudCore Business>
Wazuh's indexer is memory-hungry. The CloudCore Business plan gives you a comfortable runway:>
- 8 vCPU cores
- 24 GB RAM
- 200 GB NVMe SSD
- Unmetered bandwidth
- Flat monthly price>
This supports 100+ agents, 30 days of hot retention at ~10 GB/day, and leaves headroom for Filebeat buffering and dashboard queries. For larger deployments, see our higher-tier plans at vps-server.host/store/vps.
Connect to your server via SSH to get started:
ssh root@your-server-ipStep 1: Prepare Ubuntu 24.04
Start by updating packages and installing helper tools:
sudo apt update && sudo apt upgrade -y
sudo apt install -y curl gnupg apt-transport-https ca-certificates \
software-properties-common ufw jq unzipSet a descriptive hostname (this shows up in agent and alert metadata):
sudo hostnamectl set-hostname wazuh.yourdomain.comThe Wazuh indexer needs a raised memory map count to avoid swap thrashing:
echo "vm.max_map_count=262144" | sudo tee -a /etc/sysctl.conf
sudo sysctl -pConfigure the firewall. Lock down the indexer and manager API to localhost; only expose what must be public:
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp # SSH
sudo ufw allow 443/tcp # Dashboard via Nginx
sudo ufw allow 1514/tcp # Agent events
sudo ufw allow 1514/udp # Agent events (syslog)
sudo ufw allow 1515/tcp # Agent enrollment
sudo ufw --force enableDisable swap (the indexer does not play well with swap):
sudo swapoff -a
sudo sed -i '/ swap / s/^/#/' /etc/fstabReboot before continuing if the kernel was upgraded:
sudo rebootStep 2: Install Wazuh (All-in-One with wazuh-install.sh)
The fastest path to a working Wazuh server is the official assistant script, which installs and wires up the manager, indexer, and dashboard on a single host.
Download the installer and configuration template:
curl -sO https://packages.wazuh.com/4.11/wazuh-install.sh
curl -sO https://packages.wazuh.com/4.11/config.ymlEdit config.yml to set the server name and (optionally) the node IP:
sudo nano config.ymlChange the nodes section to match your host:
nodes:
indexer:
- name: wazuh-indexer
ip: "127.0.0.1"
server:
- name: wazuh-server
ip: "127.0.0.1"
dashboard:
- name: wazuh-dashboard
ip: "127.0.0.1"Generate the certificate bundle:
sudo bash wazuh-install.sh --generate-config-filesRun the unified install (this single command deploys the indexer, the server, and the dashboard):
sudo bash wazuh-install.sh -aExpected output (trimmed):
15/04/2026 10:15:04 INFO: Starting Wazuh installation assistant.
15/04/2026 10:15:04 INFO: Verbose logging redirected to /var/log/wazuh-install.log
15/04/2026 10:15:22 INFO: --- Dependencies ---
15/04/2026 10:15:48 INFO: --- Wazuh indexer ---
15/04/2026 10:16:02 INFO: Starting service wazuh-indexer.
15/04/2026 10:17:41 INFO: Initializing Wazuh indexer cluster security settings.
15/04/2026 10:18:09 INFO: --- Wazuh server ---
15/04/2026 10:19:55 INFO: Starting service wazuh-manager.
15/04/2026 10:20:07 INFO: --- Wazuh dashboard ---
15/04/2026 10:21:30 INFO: Starting service wazuh-dashboard.
15/04/2026 10:21:55 INFO: Installation finished.
15/04/2026 10:21:55 INFO: You can access the web interface https://<wazuh-dashboard-ip>
User: admin
Password: <SuperSecureGeneratedPassword>Record the admin password from the output — you will need it for first login.
Verify the three services are running:
sudo systemctl status wazuh-manager wazuh-indexer wazuh-dashboard --no-pagerAll three should show active (running). The API listens on 127.0.0.1:55000, the indexer on 9200, and the dashboard on 443.
Step 2 (Alternative): Install Wazuh with Docker Compose
If you prefer container isolation, reproducible deployments, or you run Wazuh alongside other Dockerized services, use the official Compose stack.
Install Docker Engine and the Compose plugin (skip if already present — see our Docker Compose guide):
curl -fsSL https://get.docker.com | sh
sudo systemctl enable --now dockerClone the Wazuh Docker repository:
git clone https://github.com/wazuh/wazuh-docker.git -b v4.11.0
cd wazuh-docker/single-nodeGenerate the internal certificate bundle:
docker compose -f generate-indexer-certs.yml run --rm generatorEdit the credentials in docker-compose.yml:
services:
wazuh.manager:
environment:
- INDEXER_URL=https://wazuh.indexer:9200
- INDEXER_USERNAME=admin
- INDEXER_PASSWORD=ChangeMeStrong123!
- FILEBEAT_SSL_VERIFICATION_MODE=full
wazuh.indexer:
environment:
- "OPENSEARCH_JAVA_OPTS=-Xms4g -Xmx4g"
wazuh.dashboard:
environment:
- WAZUH_API_URL=https://wazuh.manager
- DASHBOARD_USERNAME=kibanaserver
- DASHBOARD_PASSWORD=ChangeMeStrong456!Bring the stack up:
docker compose up -d
docker compose psThe dashboard is published on https://<server-ip>:443. Default credentials are admin / SecretPassword unless you change them in internal_users.yml (strongly recommended before any production use).
Step 3: First Dashboard Login
Open a browser to https://<your-server-ip>/ (accept the self-signed warning — we replace the cert with Let's Encrypt in Step 12).
Log in with:
- Username:
admin - Password: the string printed by
wazuh-install.sh(or the one you set in Compose)
Immediately rotate the default passwords using the built-in helper:
sudo /usr/share/wazuh-indexer/plugins/opensearch-security/tools/wazuh-passwords-tool.sh -au admin -acYou will be prompted for the current admin password and a new one. Repeat for kibanaserver, wazuh, and any other internal users the tool lists.
Restart the indexer and dashboard so the new hash takes effect:
sudo systemctl restart wazuh-indexer wazuh-dashboardStep 4: Enroll Linux Agents
Agents are how Wazuh gets eyes on your monitored hosts. Install the agent on every server you want to protect — web hosts, database nodes, mail servers, Kubernetes worker nodes, developer workstations.
On each monitored Ubuntu/Debian host, add the Wazuh repository:
curl -s https://packages.wazuh.com/key/GPG-KEY-WAZUH | \ sudo gpg --no-default-keyring --keyring gnupg-ring:/usr/share/keyrings/wazuh.gpg --import sudo chmod 644 /usr/share/keyrings/wazuh.gpgecho "deb [signed-by=/usr/share/keyrings/wazuh.gpg] https://packages.wazuh.com/4.x/apt/ stable main" | \ sudo tee /etc/apt/sources.list.d/wazuh.list
sudo apt update
Install the agent, pointing at the manager IP/hostname:
sudo WAZUH_MANAGER="wazuh.yourdomain.com" WAZUH_AGENT_NAME="$(hostname)" \
apt install -y wazuh-agentEnable and start:
sudo systemctl daemon-reload
sudo systemctl enable --now wazuh-agentFor RHEL/AlmaLinux/Rocky, use the yum repo:
sudo rpm --import https://packages.wazuh.com/key/GPG-KEY-WAZUH
cat <<EOF | sudo tee /etc/yum.repos.d/wazuh.repo
[wazuh]
gpgcheck=1
gpgkey=https://packages.wazuh.com/key/GPG-KEY-WAZUH
enabled=1
name=EL-\$releasever - Wazuh
baseurl=https://packages.wazuh.com/4.x/yum/
protect=1
EOF
sudo WAZUH_MANAGER="wazuh.yourdomain.com" yum install -y wazuh-agent
sudo systemctl enable --now wazuh-agentBack on the manager, confirm the agent has registered:
sudo /var/ossec/bin/agent_control -lExpected output:
Wazuh agent_control. List of available agents:
ID: 000, Name: wazuh-server (server), IP: 127.0.0.1, Active/Local
ID: 001, Name: web01, IP: 10.0.0.11, Active
ID: 002, Name: db01, IP: 10.0.0.12, ActiveOr browse to Endpoints summary in the dashboard to see the same list with OS, version, and last-seen timestamps.
Step 5: Enroll Windows Agents
Wazuh's Windows agent collects Event Log, Sysmon, and PowerShell channels — which is where almost every interesting Windows-side detection happens.
On the Windows target (Server 2016+ or Windows 10/11), open an elevated PowerShell:
Invoke-WebRequest -Uri https://packages.wazuh.com/4.x/windows/wazuh-agent-4.11.0-1.msi-OutFile $env:TEMP\wazuh-agent.msi
msiexec.exe /i $env:TEMP\wazuh-agent.msi /qWAZUH_MANAGER="wazuh.yourdomain.com"WAZUH_AGENT_NAME="$env:COMPUTERNAME"WAZUH_REGISTRATION_SERVER="wazuh.yourdomain.com"
NET START WazuhSvc
To collect Sysmon events (highly recommended — it is the single biggest uplift for Windows detection quality), install Sysmon with a well-known config such as SwiftOnSecurity's:
Invoke-WebRequest -Uri https://live.sysinternals.com/Sysmon64.exe -OutFile C:\Windows\Sysmon64.exe
Invoke-WebRequest -Uri https://raw.githubusercontent.com/SwiftOnSecurity/sysmon-config/master/sysmonconfig-export.xml -OutFile C:\sysmonconfig.xml
C:\Windows\Sysmon64.exe -accepteula -i C:\sysmonconfig.xmlThen tell the Wazuh agent to forward the Sysmon channel. Edit C:\Program Files (x86)\ossec-agent\ossec.conf and add inside the <ossec_config> block:
<localfile>
<location>Microsoft-Windows-Sysmon/Operational</location>
<log_format>eventchannel</log_format>
</localfile>Restart the service:
Restart-Service -Name WazuhSvcThe agent now appears in the dashboard alongside Linux hosts, and Sysmon events flow into the default Wazuh ruleset which has hundreds of Sysmon-aware detections.
Step 6: Tune Rules and Decoders
Wazuh ships with a base ruleset of ~3,000 rules and ~600 decoders under /var/ossec/ruleset/. Never edit those files — they are overwritten on upgrade. Put custom rules and decoders in /var/ossec/etc/rules/ and /var/ossec/etc/decoders/.
Create a custom rule that fires on repeated SSH failures from the same IP (an example — the default ruleset already covers this, but the pattern is the thing):
sudo nano /var/ossec/etc/rules/local_rules.xml<group name="local,syslog,sshd,">
<rule id="100100" level="10" frequency="8" timeframe="120">
<if_matched_sid>5716</if_matched_sid>
<same_source_ip />
<description>Custom: 8 SSH auth failures from same IP in 2 minutes</description>
<mitre>
<id>T1110.001</id>
</mitre>
<group>authentication_failures,pci_dss_10.2.4,</group>
</rule>
</group>Validate and reload:
sudo /var/ossec/bin/wazuh-logtest
sudo systemctl restart wazuh-managerPaste a sample sshd failure line into wazuh-logtest — it will show you which decoder parsed it and which rule chain it matched.
For a custom application (say, a Node.js app logging JSON to /var/log/myapp/app.log), add a decoder in /var/ossec/etc/decoders/local_decoder.xml and a matching rule set. Reference the Wazuh ruleset docs for the full syntax.
Step 7: Enable File Integrity Monitoring (FIM)
FIM ("syscheck") hashes critical files and alerts on any change — the classic detection for webshells, cryptominers, and backdoored binaries.
On the manager, edit /var/ossec/etc/ossec.conf and adjust the <syscheck> block:
<syscheck> <disabled>no</disabled> <frequency>43200</frequency> <!-- 12h full scan --> <scan_on_start>yes</scan_on_start><!-- Realtime monitoring on high-value directories --> <directories realtime="yes" check_all="yes" report_changes="yes">/etc,/bin,/sbin,/usr/bin,/usr/sbin</directories> <directories realtime="yes" check_all="yes" report_changes="yes">/var/www</directories> <directories realtime="yes" check_all="yes" report_changes="yes">/root/.ssh,/home/*/.ssh</directories>
<!-- Weekly whole-disk baseline --> <directories check_all="yes">/usr/lib,/opt</directories>
<ignore>/etc/mtab</ignore> <ignore>/etc/hosts.deny</ignore> <ignore type="sregex">.log$|.swp$</ignore>
<nodiff>/etc/ssl/private.key</nodiff> </syscheck>
Push the shared config to all agents by bumping the centralized-config version:
sudo systemctl restart wazuh-managerAgents pull the new config within seconds. In the dashboard under Endpoint Security → File Integrity Monitoring you will see the initial baseline populate, then diffs as files change. The report_changes="yes" attribute makes Wazuh ship the textual diff of modified files — gold for web server and config file tampering.
For Windows agents, add equivalent <directories realtime="yes">C:\Windows\System32\drivers\etc</directories> and similar lines, plus a <registry> block to watch Run keys.
Step 8: Enable Vulnerability Detection
Wazuh's vulnerability detector correlates each agent's installed-package inventory against CVE feeds (NVD, Canonical, Red Hat, Debian, Microsoft, ALAS) and raises alerts mapped to the affected hosts.
In Wazuh 4.8+, vulnerability detection is enabled via the vulnerability-detection module in /var/ossec/etc/ossec.conf:
<vulnerability-detection> <enabled>yes</enabled> <index-status>yes</index-status> <feed-update-interval>60m</feed-update-interval> </vulnerability-detection>
<indexer> <enabled>yes</enabled> <hosts> <host>https://127.0.0.1:9200</host> </hosts> <ssl> <certificate_authorities> <ca>/etc/filebeat/certs/root-ca.pem</ca> </certificate_authorities> <certificate>/etc/filebeat/certs/filebeat.pem</certificate> <key>/etc/filebeat/certs/filebeat-key.pem</key> </ssl> </indexer>
Restart the manager:
sudo systemctl restart wazuh-managerOn each agent, make sure the Syscollector module is running (it is enabled by default and populates the package inventory):
<wodle name="syscollector">
<disabled>no</disabled>
<interval>1h</interval>
<packages>yes</packages>
<os>yes</os>
<hardware>yes</hardware>
<ports>yes</ports>
<processes>yes</processes>
</wodle>Within 60 minutes the dashboard's Vulnerability Detection module populates with CVE IDs, CVSS scores, affected packages, fixed versions, and per-agent exposure. Use it to prioritize patch windows — a Wazuh CVE report is usually faster to act on than a generic OS-update notice.
Step 9: Use the MITRE ATT&CK Module
Every rule in the default ruleset is tagged with the MITRE ATT&CK technique it detects. In the dashboard, open Threat Intelligence → MITRE ATT&CK to see a coverage matrix: Initial Access, Execution, Persistence, Privilege Escalation, Defense Evasion, Credential Access, Discovery, Lateral Movement, Collection, Command and Control, Exfiltration, Impact.
Clicking any technique (for example, T1110 – Brute Force) shows every alert Wazuh has recorded that maps to it, the rule IDs, and the affected agents. You can pivot from technique to rule to raw event and back.
To add ATT&CK tagging to your own custom rules, include <mitre> IDs as shown in Step 6. The dashboard will automatically incorporate them into the coverage heatmap.
The Intelligence panel in the same section lets you search Mitigations and Data Sources — useful when a detection fires and you need to look up defensive recommendations for that tactic.
Step 10: Configure Active Response
Active Response lets Wazuh execute commands on the agent in response to specific alerts — block an IP in iptables, disable a user account, kill a process, run a forensic capture script. Used carefully, this is the "R" in XDR.
Define a command and a response in /var/ossec/etc/ossec.conf on the manager:
<command> <name>firewall-drop</name> <executable>firewall-drop</executable> <timeout_allowed>yes</timeout_allowed> </command>
<active-response> <command>firewall-drop</command> <location>local</location> <rules_id>5712,5763,100100</rules_id> <timeout>600</timeout> </active-response>
This blocks the attacking IP via iptables on the affected agent for 600 seconds whenever rule 5712 (multiple auth failures), 5763 (brute-force), or our custom 100100 fires. The firewall-drop script is shipped with the agent at /var/ossec/active-response/bin/firewall-drop.
Restart the manager to push the config:
sudo systemctl restart wazuh-managerTrigger a test: from a disposable IP, attempt 10 failed SSH logins to a monitored host. Within seconds you should see the IP appear in sudo iptables -L INPUT -n --line-numbers and an active-response alert in the dashboard. After 600 seconds, the block automatically expires.
For higher-value responses (disabling a user, wiping a compromised service account session, isolating a host from the network), write custom response scripts in /var/ossec/active-response/bin/ and point to them the same way. See the Wazuh active response docs for recipes.
Step 11: Integrations — Slack, VirusTotal, Shuffle SOAR
Wazuh can push alerts to external systems via the <integration> block in /var/ossec/etc/ossec.conf.
Slack notifications
Create an Incoming Webhook in Slack, then add:
<integration>
<name>slack</name>
<hook_url>https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX</hook_url>
<level>10</level>
<alert_format>json</alert_format>
</integration>Only alerts at level 10 or higher are shipped (keeps Slack sane). Restart the manager; the next high-severity event will land in the configured channel with host, rule, and description.
VirusTotal file hash enrichment
Pair VirusTotal with FIM so every new or modified binary is queried against 70+ AV engines:
<integration>
<name>virustotal</name>
<api_key>YOUR_VT_API_KEY</api_key>
<group>syscheck</group>
<alert_format>json</alert_format>
</integration>When FIM reports a new file, Wazuh submits the SHA256 to VirusTotal. A positive detection triggers rule 87105 — which you can chain into an active response (for example, quarantine the file) or a Slack alert.
Shuffle SOAR playbooks
Shuffle is an open-source SOAR platform that pairs well with Wazuh for building alert-to-playbook automation (ticket creation, threat intel lookup, EDR containment).
<integration>
<name>shuffle</name>
<hook_url>https://shuffler.io/api/v1/hooks/webhook_YOUR_ID</hook_url>
<level>8</level>
<alert_format>json</alert_format>
</integration>In Shuffle, trigger a workflow from the webhook and chain tasks — create a Jira ticket, run a whois on the source IP, post to an incident channel, or call a cloud API to isolate an EC2 instance. This is the cheapest open-source path to genuine SOC automation.
Restart the manager after adding integrations:
sudo systemctl restart wazuh-managerStep 12: Publish the Dashboard Behind Nginx + TLS
Running the Wazuh dashboard on port 443 directly works, but a dedicated Nginx reverse proxy gives you a real Let's Encrypt certificate, custom security headers, optional IP allowlisting, and a clean path to run other services on the same host.
Point the dashboard at localhost only. Edit /etc/wazuh-dashboard/opensearch_dashboards.yml:
server.host: "127.0.0.1"
server.port: 5601Restart the dashboard:
sudo systemctl restart wazuh-dashboardInstall Nginx and Certbot:
sudo apt install -y nginx certbot python3-certbot-nginxCreate the site config:
sudo tee /etc/nginx/sites-available/wazuh > /dev/null <<'EOF' server { listen 80; server_name wazuh.yourdomain.com; return 301 https://$host$request_uri; }server { listen 443 ssl http2; server_name wazuh.yourdomain.com;
ssl_certificate /etc/letsencrypt/live/wazuh.yourdomain.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/wazuh.yourdomain.com/privkey.pem; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always; add_header X-Frame-Options DENY always; add_header X-Content-Type-Options nosniff always; add_header Referrer-Policy strict-origin-when-cross-origin always;
client_max_body_size 50m;
location / { proxy_pass https://127.0.0.1:5601; proxy_ssl_verify off; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_read_timeout 600s; } } EOF
sudo ln -s /etc/nginx/sites-available/wazuh /etc/nginx/sites-enabled/ sudo rm -f /etc/nginx/sites-enabled/default sudo nginx -t
Obtain the certificate (Certbot will autowire the cert paths above):
sudo certbot --nginx -d wazuh.yourdomain.com
sudo systemctl reload nginxVisit https://wazuh.yourdomain.com — you should now see the Wazuh dashboard behind a green padlock.
For extra hardening, restrict dashboard access by source IP:
location / {
allow 203.0.113.0/24; # Office network
allow 198.51.100.42; # VPN exit
deny all;
# ...proxy_pass block...
}Step 13: Backups and Upgrades
A SIEM is useless without its data. Back up three things: the manager configuration, the indexer data, and the shared agent configs.
Daily configuration backup
Create /usr/local/bin/wazuh-backup.sh:
#!/usr/bin/env bash
set -euo pipefailBACKUP_DIR="/var/backups/wazuh/$(date +%Y-%m-%d)"
mkdir -p "$BACKUP_DIR"
Manager config & rules
tar czf "$BACKUP_DIR/ossec-etc.tar.gz" /var/ossec/etc
tar czf "$BACKUP_DIR/ossec-ruleset.tar.gz" /var/ossec/ruleset
tar czf "$BACKUP_DIR/filebeat.tar.gz" /etc/filebeatIndexer snapshot (register repo once, snapshot nightly)
curl -sk -u admin:$INDEXER_ADMIN_PASSWORD \
-X PUT "https://127.0.0.1:9200/_snapshot/wazuh_fs" \
-H 'Content-Type: application/json' -d '{
"type": "fs",
"settings": { "location": "/mnt/backups/wazuh-snapshots" }
}' || truecurl -sk -u admin:$INDEXER_ADMIN_PASSWORD \
-X PUT "https://127.0.0.1:9200/_snapshot/wazuh_fs/snap-$(date +%Y%m%d)?wait_for_completion=false"
Retention: keep last 14 days
find /var/backups/wazuh -maxdepth 1 -type d -mtime +14 -exec rm -rf {} +Make executable and schedule via cron:
sudo chmod +x /usr/local/bin/wazuh-backup.sh
echo "0 3 * root INDEXER_ADMIN_PASSWORD='YourAdminPassword' /usr/local/bin/wazuh-backup.sh" | \
sudo tee /etc/cron.d/wazuh-backupShip /mnt/backups/wazuh-snapshots offsite (rclone to S3/B2, rsync to a second VPS, restic to a backup repo).
Upgrades
Wazuh releases patch versions monthly. Before any upgrade, take a full backup. For the all-in-one install:
sudo apt update
sudo apt install --only-upgrade wazuh-manager wazuh-indexer wazuh-dashboardAgents upgrade themselves when their host runs apt upgrade (provided the Wazuh repo is configured). For major upgrades (4.x → 5.x when it ships), read the migration notes first — breaking rule or indexer schema changes occasionally require reindexing.
Troubleshooting
| Problem | Cause | Solution |
|---|---|---|
wazuh-install.sh exits at "Wazuh indexer" step | vm.max_map_count too low or insufficient RAM | Run sudo sysctl -w vm.max_map_count=262144 and verify the VPS has 8 GB+ free. |
| Dashboard shows "No results found" | Filebeat not forwarding to the indexer | sudo filebeat test output and sudo systemctl status filebeat. Fix cert paths in /etc/filebeat/filebeat.yml. |
Agent stays in Never connected | Port 1514/1515 blocked or wrong manager address | telnet wazuh.yourdomain.com 1514 from the agent. Fix UFW or cloud firewall. |
| Agent registered but no events | Shared ruleset not pushed or time skew | Check /var/ossec/logs/ossec.log on the agent. Run sudo timedatectl set-ntp true on both sides. |
| High indexer RAM use / OOM kills | Default JVM heap too large for VPS | Edit /etc/wazuh-indexer/jvm.options, set -Xms2g / -Xmx2g for an 8 GB VPS. |
| Vulnerability detection empty | Syscollector disabled or feed download failed | Check /var/ossec/logs/ossec.log for vulnerability-detector errors. Confirm outbound HTTPS to feed.wazuh.com is allowed. |
| Slack integration silent | <level> too high or webhook URL wrong | Lower level temporarily to 3, trigger an alert, check /var/ossec/logs/integrations.log. |
| Dashboard 502 behind Nginx | Dashboard bound to wrong host or cert mismatch | Confirm server.host: "127.0.0.1" in opensearch_dashboards.yml, then curl -kI https://127.0.0.1:5601. |
Useful log files
/var/ossec/logs/ossec.log— manager, decoders, rules, active response/var/ossec/logs/api.log— management API requests/var/log/wazuh-indexer/wazuh-cluster.log— indexer/var/log/wazuh-dashboard/wazuh-dashboard.log— dashboard/var/log/filebeat/filebeat— indexer forwarding
sudo journalctl -u wazuh-manager -u wazuh-indexer -u wazuh-dashboard -u filebeat -fFAQ
What is the difference between Wazuh SIEM and Wazuh XDR?
They are the same product used differently. As a SIEM, Wazuh centralizes and correlates logs, raises alerts, and feeds compliance reports. As an XDR, it uses the agent to detect threats on endpoints and execute active responses. Because both capabilities ship in the same server and agent, you get SIEM and XDR from a single install — and the line between them is increasingly blurry across the industry.
How many agents can one Wazuh server handle?
An all-in-one node on 8 vCPU and 16 GB RAM comfortably supports 50–150 agents with the default ruleset and moderate log volume. Beyond 150 agents or 10 GB/day of ingest, split the manager, indexer, and dashboard onto dedicated VPSes and consider clustering the indexer. Wazuh's own reference architecture runs up to 25,000 agents on a distributed cluster.
Is Wazuh really free? What is "Wazuh Cloud"?
The Wazuh platform is 100% open-source (AGPLv2) with no feature gating. Wazuh Cloud is Wazuh Inc's managed hosting of the exact same platform — convenient if you do not want to operate the stack, but not required. Self-hosting on your own VPS gives you the full feature set at a fraction of the cost.
Can Wazuh replace CrowdStrike or SentinelOne?
Wazuh overlaps significantly with commercial EDRs: host-based IDS, file integrity monitoring, vulnerability detection, active response, cloud workload protection. What it lacks out of the box is kernel-level behavioral detection and the curated, vendor-maintained threat intel that commercial EDRs invest in. For many SMBs, defense-in-depth teams, and regulated shops that need auditable open-source tooling, Wazuh is a legitimate replacement. For APT-grade threat hunting in a large enterprise, Wazuh is often deployed alongside a commercial EDR rather than in place of it.
How does Wazuh compare to the ELK/Elastic SIEM?
Under the hood Wazuh forked OpenSearch (which itself forked Elasticsearch 7.10) and bundles a purpose-built security UI, agent, and ruleset. Elastic SIEM is the counterpart on the Elasticsearch side, with its own detection rules engine. Elastic SIEM is more tightly integrated with Elastic's broader observability stack; Wazuh is more focused and has stronger out-of-the-box compliance, FIM, and active response capabilities. If you are already running the Elastic Stack, layering Wazuh on top (forwarding Wazuh alerts into Elasticsearch) is a common pattern. If you are starting fresh, Wazuh is usually less work.
Can I combine Wazuh with Fail2ban, CrowdSec, or Graylog?
Yes, and it is a popular pattern. Fail2ban and CrowdSec give you immediate reflexive blocking at the host and edge; Wazuh correlates everything into a single pane of glass and keeps historical evidence. For high-volume log storage beyond Wazuh's security focus, pair it with Graylog for general-purpose log aggregation and pipelines.
Next Steps
Now that Wazuh is humming along, here is where to go next:
- Layer in edge protection with CrowdSec — Complement Wazuh's host-level detection with a behavior-based firewall at the edge. See our CrowdSec install guide.
- Add Fail2ban for fast reflexive blocking — Simple, effective, and a great companion to Wazuh's longer-horizon correlation. See Fail2ban on Ubuntu.
- Deploy Graylog for general log aggregation — Use Wazuh for security-grade events and Graylog for application, access, and audit logs.
- Wire up cloud security — Enable Wazuh's AWS, Azure, GCP, GitHub, and Office 365 modules to ingest cloud audit trails alongside host telemetry.
- Build dashboards — The Wazuh dashboard uses OpenSearch Dashboards under the hood. Author custom visualizations and saved searches for the detections that matter to your business.
- Read the upstream docs — The official Wazuh documentation is comprehensive; the Proof of Concept section in particular has great hands-on exercises for red-team-style detection tuning.
Run Wazuh on a VPS Built for SIEM Workloads>
Wazuh's indexer is CPU- and RAM-hungry, and logs eat NVMe for breakfast. Our CloudCore Business plan is sized for the job — generous cores, enough RAM to let the JVM breathe, and fast NVMe for hot retention.>
- 8 vCPU cores
- 24 GB RAM
- 200 GB NVMe SSD
- Unmetered bandwidth
- Flat monthly price, no ingest fees, no per-agent licensing>
Deploy Your Wazuh VPS Now and have a production SIEM stack running in under an hour.