← All articles
Wazuh
Multi-Tenancy
SOC
MSSP
Security Automation
SIEM
Blue Team
DevSecOps
MSP

Automating Multi-Tenant Wazuh: A Practical Guide

January 29, 2026

7 min read

Alexandra Costea Ifrim


I'm a developer and I have been doing research into SOC operations and daily struggles. During conversations somebody mentioned the Wazuh multi-tenancy setup and I thought that should be automated. So I built a tool to do it.

This article walks through what's actually happening under the hood — first manually, so you understand each piece, then shows you how to automate it with a single command.

The Problem: Multi-Tenancy in Wazuh

Wazuh is a powerful open-source SIEM, but it wasn't designed with multi-tenancy as a first-class feature. When you're an MSP or SOC managing 10, 50, or 100+ customers on a single Wazuh deployment, you need:

  1. Data isolation — Customer A should never see Customer B's security alerts
  2. Separate alert routing — Each customer's alerts should go to their own webhook/ticketing system
  3. Monitoring per tenant — Dedicated monitors watching each customer's indices
  4. Role-based access — If customers access dashboards, they should only see their data

Let's build this step by step.

Part 1: Manual Setup (The "Under the Hood" Logic)

Before we automate, let's look at how it works. We'll use acmecorp (lowercase is recommended for index compatibility) as our example tenant.

Step 1: Create an Agent Group in Wazuh

Agent groups are the foundation. When you assign an agent to a group, Wazuh tags all logs from that agent with the group name.

# First, authenticate and get a JWT token
TOKEN=$(curl -s -k -X POST "https://WAZUH_HOST:55000/security/user/authenticate" \
  -u "wazuh:your-password" | jq -r '.data.token')

# Create the agent group
curl -k -X POST "https://WAZUH_HOST:55000/groups" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"group_id": "acmecorp"}'

Step 2: Configure Index Routing (The Integration Point)

This is critical infrastructure that must be configured once on your Wazuh Manager before provisioning any tenants. It tells Filebeat to route alerts into tenant-specific indices based on the agent group.

Ensure your /etc/filebeat/filebeat.yml (on the Wazuh Manager or Indexer) is configured to use the agent.group metadata for dynamic index naming:

output.elasticsearch:
  hosts: ["https://localhost:9200"]
  # Dynamic routing: Ensures acmecorp logs go to wazuh-alerts-acmecorp-*
  index: "wazuh-alerts-%{[agent.group]}-4.x-%{+yyyy.MM.dd}"
systemctl restart filebeat

Without this, all tenants' alerts will land in the same generic index, defeating the entire isolation strategy. This is a one-time setup — once configured, it works for all current and future tenants.

Step 3: Create a Notification Channel in OpenSearch

Notification channels tell OpenSearch where to send alerts. Each tenant needs their own channel pointing to their webhook.

curl -k -X POST "https://OPENSEARCH_HOST:9200/_plugins/_notifications/configs" \
  -u "admin:your-password" \
  -H "Content-Type: application/json" \
  -d '{
    "config_id": "channel_acmecorp",
    "config": {
      "name": "AcmeCorp Alerts Channel",
      "config_type": "webhook",
      "is_enabled": true,
      "webhook": {
        "url": "https://soc.acmecorp.com/api/wazuh-alerts",
        "method": "POST"
      }
    }
  }'

Step 4: Create a Monitor

Monitors are scheduled queries. Each tenant needs one that searches only their indices and filters by their agent group.

curl -k -X POST "https://OPENSEARCH_HOST:9200/_plugins/_alerting/monitors" \
  -u "admin:your-password" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "AcmeCorp Alert Monitor",
    "type": "monitor",
    "monitor_type": "query_level_monitor",
    "schedule": { "period": { "interval": 1, "unit": "MINUTES" } },
    "inputs": [{
      "search": {
        "indices": ["wazuh-alerts-acmecorp-*"],
        "query": {
          "bool": {
            "must": [
              {"match": {"agent.group": "acmecorp"}},
              {"range": {"timestamp": {"gte": "{{period_end}}||-1m"}}}
            ]
          }
        }
      }
    }],
    "triggers": [{
      "name": "Alert Trigger",
      "actions": [{
        "name": "Send to webhook",
        "destination_id": "channel_acmecorp",
        "message_template": { "source": "{{ctx.results[0]}}" }
      }]
    }]
  }'

This monitor runs every minute, checks for new alerts in AcmeCorp's indices, and sends them to their webhook.

Step 5: Create a DLS Role (Document Level Security)

If tenants access OpenSearch Dashboards directly, you need Document Level Security to ensure they only see their own data.

curl -k -X PUT "https://OPENSEARCH_HOST:9200/_plugins/_security/api/roles/tenant_acmecorp" \
  -u "admin:your-password" \
  -H "Content-Type: application/json" \
  -d '{
    "index_permissions": [{
      "index_patterns": ["wazuh-alerts-*"],
      "dls": "{\"match\": {\"agent.group\": \"acmecorp\"}}",
      "allowed_actions": ["read", "search"]
    }]
  }'

Part 2: The Automated Way

Now that you see the complexity, let's automate it.

The Wazuh Tenant Orchestrator handles Steps 1, 3, 4, and 5 automatically (you still need to configure Filebeat once as shown in Step 2).

Installation & Setup

git clone https://github.com/lex-org/wazuh-tenant-orchestrator.git
cd wazuh-tenant-orchestrator
poetry install

Configure your .env with your Wazuh and OpenSearch credentials, then provision a tenant.

CLI Usage

python main.py --tenant "acmecorp" --webhook "https://soc.acmecorp.com/api/alerts"

REST API

The real power for an MSP/SOC comes from integration. The tool includes a built-in REST API designed to plug directly into SOAR platforms like Splunk SOAR, Tines, or Shuffle. This allows you to trigger tenant provisioning automatically when a new customer is marked as "Closed-Won" in your CRM.

All endpoints require an X-API-Key header for security.

| Endpoint | Description | |----------|-------------| | POST /api/v1/tenants | Provisions a new tenant (Group, Channel, Monitor, Role) | | GET /api/v1/tenants/{id} | Checks provisioning status and resource IDs | | DELETE /api/v1/tenants/{id} | Deprovisions a tenant and cleans up all resources |

Real-World Workflow

  1. One-time setup: Configure Filebeat routing (Step 2 above)
  2. Per-tenant onboarding: Run the script for each new tenant
  3. Agent deployment: Install Wazuh agent with group assignment
  4. Offboarding: Use the DELETE endpoint to clean up

Tips and Gotchas

  • Naming convention: Always use lowercase for your --tenant name to ensure it matches OpenSearch index naming rules perfectly.
  • Filebeat is king: Your automation is only as good as your routing. Double-check your filebeat.yml dynamic index naming.
  • DLS performance: For massive deployments (1000+ tenants), DLS adds query overhead. Consider index-level isolation as your primary defense.

Why This Matters

I posted the tool on GitHub and Reddit's netsec community. In 4 days: 82 unique visitors, 32 unique cloners, 8 GitHub stars, and +4,600 views on Reddit. For a niche tool solving a narrow problem in an already-niche SIEM platform, that's validation.

This wasn't about Wazuh specifically. It validated something bigger: small SOC teams are infrastructure-starved, and developers haven't been paying attention. They're not asking for more AI-powered detection engines or another vendor dashboard. They need tools that eliminate operational friction — automation that actually lets them do security work.


GitHub: wazuh-tenant-orchestrator

Sound familiar?

We're building SOCmate with early partner teams. If this resonates with your challenges, let's talk.

Get in touch