Wazuh Indexer API - Querying and Managing Data
Wazuh Indexer is built on OpenSearch and provides a REST API for searching, analyzing, and managing security data. Through the API you can execute DSL queries against alerts, build aggregations for analytical reports, use PPL and SQL for interactive analysis, and manage index templates and field mappings. Understanding the Indexer API is essential for building custom dashboards, automation workflows, and integrating Wazuh with external systems.
Wazuh Index Patterns
Wazuh Indexer stores data across several index types, each serving a distinct purpose with its own rotation schedule.
Core indices
| Index | Purpose | Rotation |
|---|---|---|
wazuh-alerts-* | Alerts generated when detection rules fire | Daily |
wazuh-archives-* | All events, including those that did not trigger alerts | Daily |
wazuh-monitoring-* | Agent connection status history | Weekly |
wazuh-statistics-* | Wazuh server performance metrics | Weekly |
State indices
Starting with Wazuh 4.14, inventory and vulnerability data is stored in dedicated state indices:
| Index | Contents |
|---|---|
wazuh-states-vulnerabilities-* | Detected vulnerabilities and severity ratings |
wazuh-states-inventory-packages-* | Installed software packages |
wazuh-states-inventory-processes-* | Running processes |
wazuh-states-inventory-ports-* | Open network ports |
wazuh-states-inventory-hardware-* | CPU, memory, hardware components |
wazuh-states-inventory-system-* | OS, architecture, hostname |
wazuh-states-inventory-networks-* | IPv4/IPv6 network addresses |
Listing indices
List all Wazuh indices:
curl -sk -u admin:$PASSWORD \
"https://localhost:9200/_cat/indices/wazuh-*?v&s=index"Check the size of a specific index:
curl -sk -u admin:$PASSWORD \
"https://localhost:9200/_cat/indices/wazuh-alerts-*?v&h=index,docs.count,store.size&s=index"Searching Alerts with DSL Queries
OpenSearch Query DSL (Domain Specific Language) is the primary query language for searching data in Wazuh Indexer.
Basic search (match)
Search alerts by rule description:
GET wazuh-alerts-*/_search
{
"size": 10,
"query": {
"match": {
"rule.description": "authentication failure"
}
},
"sort": [
{ "timestamp": { "order": "desc" } }
]
}Exact match (term)
Search alerts of a specific level:
GET wazuh-alerts-*/_search
{
"query": {
"term": {
"rule.level": 12
}
}
}Compound query (bool)
Combine conditions using must, should, must_not, and filter:
GET wazuh-alerts-*/_search
{
"query": {
"bool": {
"must": [
{ "match": { "rule.groups": "authentication_failed" } }
],
"filter": [
{ "range": { "timestamp": { "gte": "now-24h" } } },
{ "term": { "agent.name": "web-server-01" } }
],
"must_not": [
{ "term": { "rule.level": 3 } }
]
}
},
"sort": [{ "timestamp": "desc" }],
"size": 50
}Range query
Search alerts within a specific time period:
GET wazuh-alerts-*/_search
{
"query": {
"bool": {
"filter": [
{
"range": {
"timestamp": {
"gte": "2025-01-01T00:00:00",
"lte": "2025-01-31T23:59:59",
"format": "yyyy-MM-dd'T'HH:mm:ss"
}
}
},
{
"range": {
"rule.level": { "gte": 10 }
}
}
]
}
}
}Searching nested fields
Wazuh stores MITRE ATT&CK data in structured fields:
GET wazuh-alerts-*/_search
{
"query": {
"bool": {
"must": [
{ "term": { "rule.mitre.id": "T1110" } },
{ "term": { "rule.mitre.tactic": "Credential Access" } }
]
}
},
"_source": ["timestamp", "rule.description", "rule.level", "agent.name"]
}Wildcard and regex
Search using wildcard patterns:
GET wazuh-alerts-*/_search
{
"query": {
"wildcard": {
"data.srcip": "192.168.1.*"
}
}
}Search using regular expressions:
GET wazuh-alerts-*/_search
{
"query": {
"regexp": {
"data.url": ".*\\.(php|asp|jsp)\\?.*id=.*"
}
}
}Aggregations
Aggregations provide statistics and analytical reports over Wazuh data without retrieving individual documents.
Terms - top values
Top 10 most frequently triggered rules:
GET wazuh-alerts-*/_search
{
"size": 0,
"query": {
"range": { "timestamp": { "gte": "now-7d" } }
},
"aggs": {
"top_rules": {
"terms": {
"field": "rule.id",
"size": 10,
"order": { "_count": "desc" }
},
"aggs": {
"rule_description": {
"terms": { "field": "rule.description.keyword", "size": 1 }
}
}
}
}
}Date histogram - timeline
Alert count per hour over the last 24 hours:
GET wazuh-alerts-*/_search
{
"size": 0,
"query": {
"range": { "timestamp": { "gte": "now-24h" } }
},
"aggs": {
"alerts_over_time": {
"date_histogram": {
"field": "timestamp",
"fixed_interval": "1h"
},
"aggs": {
"by_level": {
"range": {
"field": "rule.level",
"ranges": [
{ "key": "low", "from": 0, "to": 7 },
{ "key": "medium", "from": 7, "to": 12 },
{ "key": "critical", "from": 12, "to": 16 }
]
}
}
}
}
}
}Cardinality - unique values
Count of unique agents, IP addresses, and rules:
GET wazuh-alerts-*/_search
{
"size": 0,
"query": {
"range": { "timestamp": { "gte": "now-24h" } }
},
"aggs": {
"unique_agents": {
"cardinality": { "field": "agent.id" }
},
"unique_source_ips": {
"cardinality": { "field": "data.srcip" }
},
"unique_rules": {
"cardinality": { "field": "rule.id" }
}
}
}Nested aggregations
Top 5 agents broken down by MITRE ATT&CK tactics:
GET wazuh-alerts-*/_search
{
"size": 0,
"aggs": {
"by_agent": {
"terms": { "field": "agent.name", "size": 5 },
"aggs": {
"by_tactic": {
"terms": { "field": "rule.mitre.tactic", "size": 5 }
},
"max_level": {
"max": { "field": "rule.level" }
}
}
}
}
}Index Templates and Field Mappings
Viewing the current template
curl -sk -u admin:$PASSWORD \
"https://localhost:9200/_template/wazuh?pretty"Key Wazuh alert fields
| Field | Type | Description |
|---|---|---|
timestamp | date | Event timestamp |
rule.id | keyword | Rule identifier |
rule.level | integer | Severity level (0-15) |
rule.description | text/keyword | Rule description |
rule.groups | keyword | Rule groups |
rule.mitre.id | keyword | MITRE ATT&CK technique ID |
rule.mitre.tactic | keyword | MITRE ATT&CK tactic |
agent.id | keyword | Agent identifier |
agent.name | keyword | Agent name |
agent.ip | ip | Agent IP address |
data.srcip | ip | Source IP address |
data.dstip | ip | Destination IP address |
data.srcport | integer | Source port |
data.dstport | integer | Destination port |
location | keyword | Log source |
full_log | text | Full log message |
Updating the template
To add a custom index pattern:
# Download the current template
curl -so template.json \
"https://raw.githubusercontent.com/wazuh/wazuh/v4.14.4/extensions/elasticsearch/7.x/wazuh-template.json"
# Add a custom pattern to index_patterns
# Upload the updated template
curl -sk -u admin:$PASSWORD \
-XPUT "https://localhost:9200/_template/wazuh-custom" \
-H "Content-Type: application/json" \
-d @template.jsonMapping conflicts
When a field has different types across indices, a mapping conflict occurs. To diagnose:
curl -sk -u admin:$PASSWORD \
"https://localhost:9200/wazuh-alerts-*/_mapping/field/data.srcip?pretty"Resolution: the field is defined with different types in different indices. Update the template and reindex the data, or use the keyword suffix:
{ "term": { "rule.description.keyword": "Authentication failure" } }Dev Tools Console in Dashboard
Wazuh Dashboard includes the Dev Tools console (inherited from OpenSearch Dashboards) that allows executing API requests without curl.
Accessing Dev Tools
- Open Wazuh Dashboard
- Navigate to OpenSearch Dashboards - Dev Tools
- Or use the URL:
https://<dashboard>:443/app/dev_tools#/console
Usage examples
In the Dev Tools console, you do not need to specify the URL or authentication:
# Check cluster health
GET _cluster/health
# Search for recent critical alerts
GET wazuh-alerts-*/_search
{
"size": 5,
"query": {
"bool": {
"filter": [
{ "range": { "rule.level": { "gte": 12 } } },
{ "range": { "timestamp": { "gte": "now-1h" } } }
]
}
},
"sort": [{ "timestamp": "desc" }]
}
# Agent statistics
GET wazuh-alerts-*/_search
{
"size": 0,
"aggs": {
"agents": {
"terms": { "field": "agent.name", "size": 20 }
}
}
}Autocompletion
Dev Tools supports autocompletion for index names, fields, and DSL keywords. Use Ctrl+Space to trigger suggestions.
PPL - Piped Processing Language
PPL provides a Unix-pipe-like syntax for querying data. It is convenient for analysts familiar with Splunk SPL.
Basic syntax
source = wazuh-alerts-* | where rule.level >= 10 | sort - timestamp | head 20Filtering and aggregation
source = wazuh-alerts-*
| where timestamp > '2025-01-01 00:00:00'
| where rule.level >= 7
| stats count() as alert_count by rule.id, rule.description
| sort - alert_count
| head 10Executing PPL via API
curl -sk -u admin:$PASSWORD \
-XPOST "https://localhost:9200/_plugins/_ppl" \
-H "Content-Type: application/json" \
-d '{
"query": "source = wazuh-alerts-* | where rule.level >= 12 | stats count() by agent.name | sort - count()"
}'Time filters in PPL
source = wazuh-alerts-*
| where timestamp > DATE_SUB(NOW(), INTERVAL 24 HOUR)
| stats count() as alerts by span(timestamp, 1h) as hour
| sort hourGrouping with multiple metrics
source = wazuh-alerts-*
| where timestamp > DATE_SUB(NOW(), INTERVAL 7 DAY)
| stats count() as total, max(rule.level) as max_level, dc(agent.id) as agents
by rule.mitre.tactic
| sort - totalSQL Plugin
The OpenSearch SQL plugin allows using familiar SQL syntax to query Wazuh data.
Basic queries
SELECT timestamp, rule.id, rule.level, rule.description, agent.name
FROM wazuh-alerts-*
WHERE rule.level >= 10
AND timestamp > NOW() - INTERVAL 24 HOUR
ORDER BY timestamp DESC
LIMIT 50SQL aggregations
SELECT rule.id, rule.description, COUNT(*) as count
FROM wazuh-alerts-*
WHERE timestamp > NOW() - INTERVAL 7 DAY
GROUP BY rule.id, rule.description
ORDER BY count DESC
LIMIT 10Executing SQL via API
curl -sk -u admin:$PASSWORD \
-XPOST "https://localhost:9200/_plugins/_sql" \
-H "Content-Type: application/json" \
-d '{
"query": "SELECT agent.name, COUNT(*) as alerts FROM wazuh-alerts-* WHERE rule.level >= 7 GROUP BY agent.name ORDER BY alerts DESC"
}'Translating SQL to DSL
Useful for debugging - convert SQL to the equivalent DSL query:
curl -sk -u admin:$PASSWORD \
-XPOST "https://localhost:9200/_plugins/_sql/_explain" \
-H "Content-Type: application/json" \
-d '{
"query": "SELECT * FROM wazuh-alerts-* WHERE rule.level >= 12 LIMIT 10"
}'Bulk Operations
Bulk operations are useful for data management and automation workflows.
Bulk search (msearch)
Execute multiple queries in a single call:
curl -sk -u admin:$PASSWORD \
-XPOST "https://localhost:9200/_msearch" \
-H "Content-Type: application/x-ndjson" \
-d '
{"index":"wazuh-alerts-*"}
{"size":0,"query":{"range":{"rule.level":{"gte":12}}},"aggs":{"count":{"value_count":{"field":"_id"}}}}
{"index":"wazuh-alerts-*"}
{"size":0,"query":{"range":{"timestamp":{"gte":"now-1h"}}},"aggs":{"count":{"value_count":{"field":"_id"}}}}
'Scroll API for large result sets
When you need to retrieve more than 10,000 documents, use the Scroll API:
# Initialize scroll
curl -sk -u admin:$PASSWORD \
-XPOST "https://localhost:9200/wazuh-alerts-*/_search?scroll=5m" \
-H "Content-Type: application/json" \
-d '{
"size": 1000,
"query": { "range": { "timestamp": { "gte": "now-30d" } } },
"sort": [{ "timestamp": "asc" }]
}'
# Continue scrolling (use _scroll_id from the response)
curl -sk -u admin:$PASSWORD \
-XPOST "https://localhost:9200/_search/scroll" \
-H "Content-Type: application/json" \
-d '{
"scroll": "5m",
"scroll_id": "<SCROLL_ID>"
}'Delete by query
Remove old low-level alerts:
curl -sk -u admin:$PASSWORD \
-XPOST "https://localhost:9200/wazuh-alerts-*/_delete_by_query" \
-H "Content-Type: application/json" \
-d '{
"query": {
"bool": {
"filter": [
{ "range": { "timestamp": { "lte": "now-90d" } } },
{ "range": { "rule.level": { "lte": 3 } } }
]
}
}
}'Comparison with Other SIEM Platforms
Elasticsearch Query DSL
Wazuh Indexer (OpenSearch) uses syntax fully compatible with Elasticsearch 7.x Query DSL. Key differences:
| Feature | Wazuh Indexer (OpenSearch) | Elasticsearch |
|---|---|---|
| Core DSL | Identical syntax | Identical syntax |
| SQL Plugin | _plugins/_sql | _sql (X-Pack) |
| PPL | _plugins/_ppl | Not available |
| Alerting | _plugins/_alerting | X-Pack Watcher |
| Security | Security Plugin (built-in) | X-Pack Security (paid) |
Migrating queries from Elasticsearch to Wazuh Indexer requires no changes to DSL syntax.
Splunk SPL
| Task | Wazuh PPL | Splunk SPL |
|---|---|---|
| Search | source = index | where field = 'value' | index=main field=value |
| Aggregation | stats count() by field | stats count by field |
| Sorting | sort - field | sort - field |
| Limit | head N | head N |
| Time filter | where timestamp > ... | earliest=-24h |
PPL in OpenSearch is closest to SPL in syntax, making migration easier for Splunk analysts.
QRadar AQL
| Task | Wazuh SQL | QRadar AQL |
|---|---|---|
| Event search | SELECT * FROM wazuh-alerts-* | SELECT * FROM events |
| Filtering | WHERE rule.level >= 10 | WHERE severity >= 7 |
| Aggregation | GROUP BY rule.id | GROUP BY qid |
| Time filter | WHERE timestamp > NOW() - INTERVAL 1 HOUR | WHERE starttime > NOW - 1 HOURS |
SQL syntax in OpenSearch is close to standard SQL, making adaptation straightforward for QRadar users.
Troubleshooting
Slow queries
Symptom: queries take longer than 10 seconds to execute.
Diagnostics:
# Check cluster load
curl -sk -u admin:$PASSWORD "https://localhost:9200/_nodes/stats/os,jvm?pretty"
# Enable slow query logging
curl -sk -u admin:$PASSWORD \
-XPUT "https://localhost:9200/wazuh-alerts-*/_settings" \
-H "Content-Type: application/json" \
-d '{
"index.search.slowlog.threshold.query.warn": "5s",
"index.search.slowlog.threshold.query.info": "2s"
}'
# View slow log indices
curl -sk -u admin:$PASSWORD \
"https://localhost:9200/_cat/indices/.opendistro-slow-log-*?v"Solutions:
- Use
filterinstead ofmustfor conditions that do not require relevance scoring - Limit the time range of your query - avoid querying all historical data
- Add
"size": 0when you only need aggregations - Avoid
wildcardandregexpon fields without keyword mapping - Increase JVM heap if garbage collection runs too frequently
Field mapping conflicts
Symptom: illegal_argument_exception error when running a query.
# Check the mapping of a specific field
curl -sk -u admin:$PASSWORD \
"https://localhost:9200/wazuh-alerts-*/_mapping/field/data.srcip?pretty"Resolution: the field is defined with different types across indices. Update the template and reindex the data, or use the keyword suffix:
{ "term": { "rule.description.keyword": "Authentication failure" } }Index not found (index_not_found_exception)
Symptom: no such index [wazuh-alerts-*] error.
Diagnostics:
# Check existing indices
curl -sk -u admin:$PASSWORD "https://localhost:9200/_cat/indices/wazuh-*?v"
# Check aliases
curl -sk -u admin:$PASSWORD "https://localhost:9200/_cat/aliases/wazuh-*?v"
# Verify that Filebeat is sending data
curl -sk -u admin:$PASSWORD \
"https://localhost:9200/_cat/indices/wazuh-alerts-*?v&s=index:desc&h=index,docs.count,store.size"Solutions:
- Verify that Filebeat is running and connected to the Indexer
- Check Filebeat configuration:
filebeat.ymlmust contain the correctoutput.elasticsearch.hosts - Verify the index template:
_template/wazuh - For archive data, ensure
logall_jsonis enabled inossec.conf
Result window too large
Symptom: Result window is too large error when querying with "from": 10000.
OpenSearch limits from + size to 10,000 by default. Solutions:
- Use the Scroll API for full data export
- Use
search_afterfor pagination - For analytics, use aggregations instead of fetching individual documents
GET wazuh-alerts-*/_search
{
"size": 100,
"query": { "match_all": {} },
"sort": [{ "timestamp": "desc" }, { "_id": "asc" }],
"search_after": ["2025-01-15T10:30:00.000Z", "abc123"]
}Additional Resources
- Wazuh Indexer Installation - deployment and configuration
- Wazuh Dashboard Configuration - data visualization and analysis
- Wazuh Architecture - platform component overview
- Log Data Collection - configuring data sources