Tutorial

Self-host Wazuh on a VPS: full SOC in under an hour

Security & Monitoring9 min read9 steps

The NIS2 directive requires essential and important entities to implement logging, monitoring and incident response tools (article 21). At the same time, recent CVEs on Jira, Confluence and GitLab are a reminder that an unobserved perimeter is a silently compromised one. Wazuh is an active open source SIEM that covers exactly these three requirements — and its official Docker Compose distribution allows you to deploy it on a single VPS node in under an hour, with no prior security infrastructure expertise.

Contents· Why Wazuh for NIS2 art. 21 compliance1/11
  1. 01Why Wazuh for NIS2 art. 21 compliance
  2. 02Wazuh vs self-hosted SIEM alternatives
  3. 03Hardware and software prerequisites
  4. 04What Wazuh monitors as soon as agents are enabled
  5. 05Step-by-step deployment on a Debian/Ubuntu VPS
  6. 06Single-node stack architecture
  7. 07Configuring retention and email alerts
  8. 08Generating NIS2 / GDPR compliance reports
  9. 09Updating Wazuh without losing data
  10. 10Troubleshooting — common errors
  11. 11Frequent errors and remedies

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

ToolLicenseDeployment stackNIS2 art. 21 coverage
Wazuh 4.14GPLv2 (manager) / Apache 2 (indexer)Official Docker ComposeLogging + detection + active response
Elastic SIEMSSPL (Elastic Stack)Docker or packagesLogging + detection (response via external SOAR)
Graylog OpenSSPLDocker ComposeLogging (limited detection without plugins)
Security OnionGPLv2Bare-metal ISO or VMFull 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/bin by 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

  1. 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 docker

    Add your non-root user to the docker group if needed:

    usermod -aG docker $USER
  2. Configure 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.conf

    Without this step, the wazuh.indexer container stops with the error max virtual memory areas vm.max_map_count [65530] is too low.

  3. 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 main docker-compose.yml and the generate-indexer-certs.yml needed in the next step.

  4. 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 generator

    Certificates are placed in config/wazuh_indexer_ssl_certs/. Do not regenerate them without recreating the entire stack — certificates are mutually signed.

  5. Start the stack

    Launch all three services in the background:

    docker compose up -d

    Follow the startup in real time:

    docker compose logs -f

    The indexer needs about 60 seconds to be ready. Wait for the line Node 'node-1' initialized before continuing.

  6. Access the dashboard and change the password

    Open https://YOUR-VPS-IP in your browser. Accept the self-signed certificate warning for the first connection. Default credentials: user admin, password SecretPassword. 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!'
  7. 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 enable

    Restrict port 55000 to your management IPs only (ufw allow from 203.0.113.10 to any port 55000).

  8. 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-agent

    The agent appears in the dashboard under Agents within 30 seconds of registration.

  9. Further reading — related guides

    To strengthen your security posture beyond Wazuh, see our related guides: [nis2-securite-informatique-hebergement-obligations] for detailed legal obligations, [crowdsec-vs-fail2ban-securite-vps] for collaborative upstream network-level blocking, and [zitadel-sso-self-hosted-vps] to centralize authentication and reduce the attack surface on your Wazuh dashboard.

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.manager

Generating 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 /data

For 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. Run sysctl -w vm.max_map_count=262144 and verify with sysctl vm.max_map_count before restarting the stack.
  • Connection refused on port 443 after docker compose up -d — The indexer is not yet ready. Wait 60 to 90 seconds and check docker compose logs wazuh.indexer to confirm node initialization.
  • Agent disconnected right after registration — Port 1514/TCP or 1515/TCP is blocked on the VPS side. Check ufw status and your hosting provider's network security rules (cloud security groups).
  • certificate verify failed in 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 that WAZUH_DASHBOARD_HOST in docker-compose.yml matches your IP or domain name.
  • Wazuh API 401 Unauthorized — The admin password change was not propagated to the dashboard configuration. After modifying the password, also update DASHBOARD_PASSWORD in the .env file or in the wazuh.dashboard service environment variables, then restart with docker compose restart wazuh.dashboard.

A VPS built for Wazuh

Our professional VPS configurations are optimized for SIEM workloads: 8 vCPU, 16 GB RAM, fast NVMe storage and isolated networking. Deploy Wazuh in production with the bandwidth and resources it deserves.

Need help?

Browse our help center and FAQ, or reach our team — callback, WhatsApp or email. Support in French, English and Arabic.

Message us on WhatsAppopens in a new tab