Why Wazuh for NIS2 art. 21 compliance
Article 21 of NIS2 mandates four capabilities: event logging, incident detection, response, and notification. Wazuh covers the first three natively — log collection, detection rules, active response — and its dashboard produces the audit reports needed for the fourth. Unlike a SaaS SIEM, data stays on your infrastructure: no transfer to third-party servers, which also simplifies GDPR compliance for entities processing personal data. Version 4.14.8 (released September 25, 2026) is the current stable release; it bundles the Wazuh manager, indexer (OpenSearch-based search engine) and dashboard in a single Docker Compose stack.
Wazuh vs self-hosted SIEM alternatives
Scroll the table
| Tool | License | Deployment stack | NIS2 art. 21 coverage |
|---|---|---|---|
| Wazuh 4.14 | GPLv2 (manager) / Apache 2 (indexer) | Official Docker Compose | Logging + detection + active response |
| Elastic SIEM | SSPL (Elastic Stack) | Docker or packages | Logging + detection (response via external SOAR) |
| Graylog Open | SSPL | Docker Compose | Logging (limited detection without plugins) |
| Security Onion | GPLv2 | Bare-metal ISO or VM | Full coverage but requires a dedicated server |
Hardware and software prerequisites
The official requirements for a single-node deployment covering up to 25 agents are: 4 vCPU, 8 GB RAM and 50 GB storage (for 90 days of indexed data). A mid-range VPS is sufficient for a team monitoring up to 25 endpoints or servers; beyond 50 agents, move to 8 vCPU and consider an additional volume. On the software side, you need Docker Engine (version 24 minimum), Docker Compose v2, and Git. The Linux kernel must allow at least 262,144 virtual memory areas for the indexer — without this the service refuses to start.
What Wazuh monitors as soon as agents are enabled
- File integrity monitoring (FIM) — detects any modification, creation or deletion on critical paths (
/etc,/bin,/usr/binby default) - System logs — collects and indexes authentication logs, syslog, journald and configured application logs
- Configuration assessment (SCA) — compares actual system state to CIS benchmarks for Ubuntu, Debian, RHEL and Windows
- Rootkit detection — analyzes hidden processes and kernel anomalies via
rootcheck - Vulnerability detection — correlates installed packages on agents with CVEs from the National Vulnerability Database
- Active response — automatically blocks an IP after N failed authentication attempts, without a third-party SOAR agent
- Cloud logs — ingests AWS CloudTrail, Azure Activity Log and GCP Audit Log events via dedicated modules
Step-by-step deployment on a Debian/Ubuntu VPS
Prepare the system
Update packages and install dependencies:
apt update && apt upgrade -y apt install -y docker.io docker-compose-v2 git curl systemctl enable --now dockerAdd your non-root user to the docker group if needed:
usermod -aG docker $USERConfigure vm.max_map_count
The Wazuh indexer (OpenSearch) requires at least 262,144 virtual memory areas. Apply the parameter immediately and make it permanent:
sysctl -w vm.max_map_count=262144 echo 'vm.max_map_count=262144' >> /etc/sysctl.confWithout this step, the
wazuh.indexercontainer stops with the errormax virtual memory areas vm.max_map_count [65530] is too low.Clone the official repository
Clone the branch matching the current stable version:
git clone https://github.com/wazuh/wazuh-docker.git -b v4.14.8 cd wazuh-docker/single-node/The
single-node/directory contains the maindocker-compose.ymland thegenerate-indexer-certs.ymlneeded in the next step.Generate TLS inter-service certificates
Wazuh encrypts all internal communications (manager → indexer → dashboard). Generate certificates with the provided utility:
docker compose -f generate-indexer-certs.yml run --rm generatorCertificates are placed in
config/wazuh_indexer_ssl_certs/. Do not regenerate them without recreating the entire stack — certificates are mutually signed.Start the stack
Launch all three services in the background:
docker compose up -dFollow the startup in real time:
docker compose logs -fThe indexer needs about 60 seconds to be ready. Wait for the line
Node 'node-1' initializedbefore continuing.Access the dashboard and change the password
Open
https://YOUR-VPS-IPin your browser. Accept the self-signed certificate warning for the first connection. Default credentials: useradmin, passwordSecretPassword. Change the password immediately via Security → Internal users → admin → Edit. For command-line automation:docker exec -it single-node-wazuh.manager-1 \ /var/ossec/bin/wazuh-passwords-tool.sh \ -u admin -p 'NewPassword!'Open the required firewall ports
On the VPS, allow inbound traffic for agents and the dashboard:
ufw allow 443/tcp # Dashboard HTTPS ufw allow 1514/tcp # Agents (Wazuh protocol) ufw allow 1515/tcp # Agent registration ufw allow 514/udp # Syslog reception ufw allow 55000/tcp # Wazuh REST API (restrict to your IPs) ufw enableRestrict port 55000 to your management IPs only (
ufw allow from 203.0.113.10 to any port 55000).Install the Wazuh agent on a server to monitor
On each machine to observe, install the agent and point it at your VPS. Example on Debian/Ubuntu:
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" \ > /etc/apt/sources.list.d/wazuh.list apt update && apt install -y wazuh-agent WAZUH_MANAGER='YOUR-VPS-IP' \ WAZUH_AGENT_NAME='your-server-01' \ dpkg-reconfigure wazuh-agent systemctl enable --now wazuh-agentThe agent appears in the dashboard under Agents within 30 seconds of registration.
Single-node stack architecture
The official Docker Compose stack deploys three interdependent services. The Wazuh manager is the core: it receives events from agents on port 1514, applies detection rules, triggers active responses and forwards alerts to the indexer. The Wazuh indexer (OpenSearch) stores and makes those alerts searchable; it communicates with the manager on port 9200 and requires the kernel resources specific to a search engine. The Wazuh dashboard exposes the user interface on port 443 and queries the indexer over HTTPS. All three services share self-signed TLS certificates generated during the preparation step; modifying one without regenerating the others causes mutual validation errors. Persistent data — manager configuration, alert queue, index data — is stored in named Docker volumes, decoupled from the container lifecycle: an update by image replacement does not lose historical data.
Configuring retention and email alerts
By default, Wazuh retains data for 90 days. To adjust retention, modify the max_age parameter in the indexer ISM policy, accessible via Index Management → Policies in the dashboard. You can extend retention to 180 days to meet NIS2 log retention requirements, or reduce it to 30 days on a storage-constrained VPS. For email alerts, edit /var/ossec/etc/ossec.conf in the manager container — add an <email_notification> block with your SMTP server and desired alert levels (level 7 and above is a good starting point for critical alerts, level 5 to include medium alerts). Wazuh can also send notifications via webhooks to Slack, Teams or PagerDuty through its integration mechanism. Restart the manager after each configuration change:
docker compose restart wazuh.managerGenerating NIS2 / GDPR compliance reports
The Wazuh dashboard includes dedicated dashboards for compliance frameworks: GDPR, PCI DSS, HIPAA and NIST 800-53. These dashboards allow you to export PDF reports listing covered controls and associated events — useful for justifying your posture to an auditor or a NIS2 competent authority. For NIS2 specifically, use the GDPR view as a base (logging and detection obligations overlap significantly) and complement it with incident detection rules classified by Mitre ATT&CK category available under Threat intelligence → Mitre ATT&CK. NIS2 requires notification within 72 hours of detecting a significant incident: configure level-12 (critical) alerts to trigger an immediate notification — Wazuh includes ready-to-use rules for known CVE exploitation attempts, lateral movement and privilege escalation. The Security events overview filtered by agent provides the chronological context needed to draft the regulatory notification.
Enable the strong password policy in Wazuh as soon as it is installed: under Security → Security settings, check 'Require complex passwords' and 'Enable audit logging'. Both options are disabled by default and must be enabled manually before going to production.
Updating Wazuh without losing data
Indexer data is persisted in named Docker volumes. The update procedure consists of cloning the new version of the repository into a separate directory, copying the existing volumes, then restarting the stack. The official documentation recommends backing up volumes before any major update:
docker run --rm \
-v single-node_wazuh-indexer-data:/data \
-v $(pwd)/backup:/backup \
alpine tar czf /backup/indexer-data-$(date +%Y%m%d).tar.gz /dataFor a minor version update (e.g. 4.14.7 → 4.14.8), simply update the tag in docker-compose.yml and restart with docker compose up -d --pull always.
Troubleshooting — common errors
Here are the five most common errors during a first Wazuh Docker deployment, with their causes and remedies.
Frequent errors and remedies
max virtual memory areas vm.max_map_count [65530] is too low— The kernel parameter was not applied. Runsysctl -w vm.max_map_count=262144and verify withsysctl vm.max_map_countbefore restarting the stack.Connection refusedon port 443 afterdocker compose up -d— The indexer is not yet ready. Wait 60 to 90 seconds and checkdocker compose logs wazuh.indexerto confirm node initialization.Agent disconnectedright after registration — Port 1514/TCP or 1515/TCP is blocked on the VPS side. Checkufw statusand your hosting provider's network security rules (cloud security groups).certificate verify failedin dashboard logs — Auto-generated certificates do not match the IP or hostname being used. Regenerate certificates (docker compose -f generate-indexer-certs.yml run --rm generator) after verifying thatWAZUH_DASHBOARD_HOSTindocker-compose.ymlmatches your IP or domain name.Wazuh API 401 Unauthorized— Theadminpassword change was not propagated to the dashboard configuration. After modifying the password, also updateDASHBOARD_PASSWORDin the.envfile or in thewazuh.dashboardservice environment variables, then restart withdocker compose restart wazuh.dashboard.