ELK Stack — Bool Queries
Introduction
Real-world search requirements are rarely satisfied by a single query clause. You need to combine conditions — “find error logs from the last hour that are NOT from the health-check endpoint.” The bool query is the workhorse that makes this possible.
A bool query lets you combine any number of query clauses using boolean logic, with full control over which clauses affect scoring and which act as filters.
Bool Query Structure
The bool query supports four clause types:
┌──────────────────────────────────────────────────────────┐
│ Bool Query │
├──────────────┬───────────────────────────────────────────┤
│ must │ All clauses MUST match (AND) │
│ │ → Contributes to score │
├──────────────┼───────────────────────────────────────────┤
│ should │ At least one SHOULD match (OR) │
│ │ → Contributes to score │
│ │ → Optional if must/filter present │
├──────────────┼───────────────────────────────────────────┤
│ must_not │ Clauses MUST NOT match (NOT) │
│ │ → Filter context (no scoring) │
│ │ → Results are cached │
├──────────────┼───────────────────────────────────────────┤
│ filter │ Clauses MUST match (AND) │
│ │ → Filter context (no scoring) │
│ │ → Results are cached │
├──────────────┴───────────────────────────────────────────┤
│ Scoring: must + should = final _score │
│ filter + must_not = no score contribution │
└──────────────────────────────────────────────────────────┘
Basic Bool Query
Let’s find GET requests that had errors (status >= 400):
curl -s -X GET "localhost:9200/web-logs/_search?pretty" -H 'Content-Type: application/json' -d '{
"query": {
"bool": {
"must": [
{ "term": { "method": "GET" } }
],
"filter": [
{ "range": { "status_code": { "gte": 400 } } }
]
}
}
}'
{
"took": 2,
"hits": {
"total": { "value": 1, "relation": "eq" },
"max_score": 0.35667494,
"hits": [
{
"_score": 0.35667494,
"_source": {
"method": "GET",
"url": "/api/users/42",
"status_code": 404,
"message": "User not found error occurred"
}
}
]
}
}
The must clause (method = GET) contributes to scoring. The filter clause (status >= 400) does not affect the score but is cached for performance.
The must Clause
All clauses inside must are required. They operate in query context (affect scoring).
curl -s -X GET "localhost:9200/web-logs/_search?pretty" -H 'Content-Type: application/json' -d '{
"query": {
"bool": {
"must": [
{ "match": { "message": "user" } },
{ "range": { "response_time": { "gt": 100 } } }
]
}
}
}'
{
"took": 3,
"hits": {
"total": { "value": 3, "relation": "eq" },
"max_score": 1.6931472,
"hits": [
{
"_score": 1.6931472,
"_source": { "message": "New user created successfully", "response_time": 350.2 }
},
{
"_score": 1.6931472,
"_source": { "message": "Successfully fetched user list", "response_time": 120.5 }
},
{
"_score": 1.6931472,
"_source": { "message": "Order history fetched for user", "response_time": 210.7 }
}
]
}
}
The should Clause
should clauses are optional boosters — they increase the score of matching documents but are not required.
Important behavior:
- If the
boolquery has nomustorfilterclauses, then at least oneshouldclause must match. - If
mustorfilteris present,shouldclauses are purely optional (score boosters).
curl -s -X GET "localhost:9200/web-logs/_search?pretty" -H 'Content-Type: application/json' -d '{
"query": {
"bool": {
"must": [
{ "match": { "message": "user" } }
],
"should": [
{ "term": { "status_code": 200 } },
{ "match": { "message": "error" } }
]
}
}
}'
{
"took": 2,
"hits": {
"total": { "value": 4, "relation": "eq" },
"max_score": 2.4849067,
"hits": [
{
"_score": 2.4849067,
"_source": { "message": "User not found error occurred", "status_code": 404 }
},
{
"_score": 1.6931472,
"_source": { "message": "Successfully fetched user list", "status_code": 200 }
},
{
"_score": 0.6931471,
"_source": { "message": "New user created successfully", "status_code": 201 }
},
{
"_score": 0.5753642,
"_source": { "message": "Order history fetched for user", "status_code": 200 }
}
]
}
}
All four documents match the must clause (“user” in message). Documents that also match a should clause get a score boost.
The must_not Clause
Excludes documents matching any of the must_not clauses. Runs in filter context — no scoring, results are cached.
curl -s -X GET "localhost:9200/web-logs/_search?pretty" -H 'Content-Type: application/json' -d '{
"query": {
"bool": {
"must": [
{ "match_all": {} }
],
"must_not": [
{ "term": { "method": "GET" } },
{ "term": { "method": "POST" } }
]
}
}
}'
{
"took": 1,
"hits": {
"total": { "value": 1, "relation": "eq" },
"max_score": 1.0,
"hits": [
{
"_score": 1.0,
"_source": {
"method": "DELETE",
"url": "/api/users/7",
"status_code": 500
}
}
]
}
}
The filter Clause
Works like must (all clauses required), but runs in filter context — no scoring, results are cached. Use filter when you don’t care about relevance ranking.
curl -s -X GET "localhost:9200/web-logs/_search?pretty" -H 'Content-Type: application/json' -d '{
"query": {
"bool": {
"filter": [
{ "term": { "method": "GET" } },
{ "range": { "response_time": { "lt": 200 } } },
{ "range": { "@timestamp": { "gte": "2026-04-01T08:00:00Z", "lt": "2026-04-01T09:00:00Z" } } }
]
}
}
}'
{
"took": 1,
"hits": {
"total": { "value": 3, "relation": "eq" },
"max_score": 0.0,
"hits": [
{ "_score": 0.0, "_source": { "method": "GET", "url": "/api/users", "response_time": 120.5 } },
{ "_score": 0.0, "_source": { "method": "GET", "url": "/api/products", "response_time": 95.3 } },
{ "_score": 0.0, "_source": { "method": "GET", "url": "/api/users/42", "response_time": 15.1 } }
]
}
}
All scores are 0.0 because everything runs in filter context.
minimum_should_match
Controls how many should clauses must match when there are no must or filter clauses:
curl -s -X GET "localhost:9200/web-logs/_search?pretty" -H 'Content-Type: application/json' -d '{
"query": {
"bool": {
"should": [
{ "match": { "message": "error" } },
{ "match": { "message": "user" } },
{ "range": { "status_code": { "gte": 400 } } }
],
"minimum_should_match": 2
}
}
}'
{
"took": 2,
"hits": {
"total": { "value": 2, "relation": "eq" },
"max_score": 2.9549103,
"hits": [
{
"_score": 2.9549103,
"_source": { "message": "User not found error occurred", "status_code": 404 }
},
{
"_score": 2.5404451,
"_source": { "message": "Internal server error during delete operation", "status_code": 500 }
}
]
}
}
Only documents matching at least 2 of the 3 should clauses are returned.
Nested Bool Queries
Bool queries can be nested inside other bool queries for complex logic:
Scenario: Find documents where:
- Method is GET or POST (group 1)
- AND status code is 200 or message contains “error” (group 2)
curl -s -X GET "localhost:9200/web-logs/_search?pretty" -H 'Content-Type: application/json' -d '{
"query": {
"bool": {
"must": [
{
"bool": {
"should": [
{ "term": { "method": "GET" } },
{ "term": { "method": "POST" } }
]
}
},
{
"bool": {
"should": [
{ "term": { "status_code": 200 } },
{ "match": { "message": "error" } }
]
}
}
]
}
}
}'
{
"took": 3,
"hits": {
"total": { "value": 4, "relation": "eq" },
"max_score": 1.5498152,
"hits": [
{ "_source": { "method": "GET", "status_code": 404, "message": "User not found error occurred" } },
{ "_source": { "method": "GET", "status_code": 200, "url": "/api/users" } },
{ "_source": { "method": "GET", "status_code": 200, "url": "/api/products" } },
{ "_source": { "method": "POST", "status_code": 201, "message": "New user created successfully" } }
]
}
}
Boosting
You can increase the importance of specific clauses using the boost parameter:
curl -s -X GET "localhost:9200/web-logs/_search?pretty" -H 'Content-Type: application/json' -d '{
"query": {
"bool": {
"should": [
{
"match": {
"message": {
"query": "error",
"boost": 3
}
}
},
{
"match": {
"message": {
"query": "user",
"boost": 1
}
}
}
]
}
}
}'
{
"took": 2,
"hits": {
"total": { "value": 5, "relation": "eq" },
"max_score": 4.1684057,
"hits": [
{
"_score": 4.1684057,
"_source": { "message": "User not found error occurred" }
},
{
"_score": 3.4893353,
"_source": { "message": "Internal server error during delete operation" }
},
{
"_score": 0.6931471,
"_source": { "message": "Successfully fetched user list" }
}
]
}
}
Documents with “error” get a much higher score due to the boost: 3 multiplier.
function_score Basics
For more advanced scoring control, function_score wraps a query and modifies scores using functions:
curl -s -X GET "localhost:9200/web-logs/_search?pretty" -H 'Content-Type: application/json' -d '{
"query": {
"function_score": {
"query": {
"match_all": {}
},
"functions": [
{
"filter": { "range": { "status_code": { "gte": 500 } } },
"weight": 10
},
{
"filter": { "range": { "status_code": { "gte": 400, "lt": 500 } } },
"weight": 5
}
],
"score_mode": "sum",
"boost_mode": "replace"
}
}
}'
{
"took": 3,
"hits": {
"total": { "value": 6, "relation": "eq" },
"max_score": 10.0,
"hits": [
{ "_score": 10.0, "_source": { "status_code": 500, "message": "Internal server error during delete operation" } },
{ "_score": 5.0, "_source": { "status_code": 404, "message": "User not found error occurred" } },
{ "_score": 1.0, "_source": { "status_code": 200, "url": "/api/users" } },
{ "_score": 1.0, "_source": { "status_code": 201, "url": "/api/users" } },
{ "_score": 1.0, "_source": { "status_code": 200, "url": "/api/products" } },
{ "_score": 1.0, "_source": { "status_code": 200, "url": "/api/orders" } }
]
}
}
Key function_score parameters:
| Parameter | Description |
|---|---|
score_mode |
How function scores combine: multiply, sum, avg, first, max, min |
boost_mode |
How function score combines with query score: multiply, replace, sum, avg, max, min |
Real-World Example: Log Search Dashboard
Here’s a practical bool query combining everything — the kind you’d use behind a log search dashboard:
curl -s -X GET "localhost:9200/web-logs/_search?pretty" -H 'Content-Type: application/json' -d '{
"query": {
"bool": {
"must": [
{ "match": { "message": "error" } }
],
"filter": [
{ "range": { "@timestamp": { "gte": "now-24h" } } },
{ "terms": { "method": ["GET", "POST", "DELETE"] } }
],
"must_not": [
{ "wildcard": { "url.raw": "/api/health*" } }
],
"should": [
{ "range": { "status_code": { "gte": 500, "boost": 5 } } }
]
}
},
"sort": [
{ "@timestamp": "desc" }
],
"size": 20
}'
This query:
- Must contain “error” in the message (scored)
- Filter to last 24 hours and specific methods (cached, no scoring)
- Exclude health-check endpoints
- Boost 5xx errors higher in results
Quick Reference
| Clause | Required? | Scoring? | Cached? | Use For |
|---|---|---|---|---|
must |
Yes (AND) | Yes | No | Conditions that should affect ranking |
should |
Depends | Yes | No | Optional boosters or OR conditions |
must_not |
No (NOT) | No | Yes | Excluding documents |
filter |
Yes (AND) | No | Yes | Required conditions without scoring |
minimum_should_match |
— | — | — | Control how many should clauses must match |
boost |
— | — | — | Increase importance of a clause |
function_score |
— | Yes | — | Custom scoring functions |
Next: Aggregations — Analyze data with metrics, buckets, and pipeline aggregations.