Searching in Elasticsearch turns stored logs and events into actionable answers for debugging, reporting, and alerting. Fast queries reduce time spent grepping raw files and make patterns visible across large datasets.

The Search API is exposed over HTTP and returns matching documents as JSON, including hit counts, scores, and optional sorting. Quick lookups can use the query-string parameter q, while more precise searches use the JSON Query DSL for boolean logic, filters, ranges, pagination, and field selection.

Query behavior depends on index mappings and analyzers, so exact matches, sorting, and ranges only work correctly when the underlying field types align with the query. Many clusters also require authentication and TLS, so the scheme (http vs https) and curl flags may need adjustment.

Steps to perform a search query in Elasticsearch:

  1. List available indices to confirm the target index name.
    $ curl -s "http://localhost:9200/_cat/indices?v&s=index"
    health status index        uuid                   pri rep docs.count docs.deleted store.size pri.store.size dataset.size
    green  open   logs-2025.01 98GlrCaeQae_bXpWVIp22g   1   0          3            0     23.9kb         23.9kb       23.9kb
    green  open   logs-2026.01 XJuWvI5iTzGbd_daA-Xh4g   1   0          1            0      5.4kb          5.4kb        5.4kb
    green  open   metrics-2026.01 2jVAXQGtQZW-IyCuDyu5Lw   1   0          1            0      4.9kb          4.9kb        4.9kb

    Clusters with security enabled typically require https plus credentials (-u user:pass) and may require a CA certificate (--cacert).

  2. Check the index mapping to confirm field names and types used in queries and sorting.
    $ curl -s "http://localhost:9200/logs-2025.01/_mapping?pretty"
    {
      "logs-2025.01" : {
        "mappings" : {
          "properties" : {
            "level" : {
              "type" : "keyword"
            },
            "message" : {
              "type" : "text"
            },
            "timestamp" : {
              "type" : "date"
            }
          }
        }
      }
    }

    Exact matches and sorting commonly use a keyword field (for example message.keyword) rather than a text field.

  3. Run a simple query string search using q with URL encoding for safe characters and spaces.
    $ curl -sG "http://localhost:9200/logs-2025.01/_search?pretty" --data-urlencode "q=timeout"
    {
      "took" : 15,
      "timed_out" : false,
      "_shards" : {
        "total" : 1,
        "successful" : 1,
        "skipped" : 0,
        "failed" : 0
      },
      "hits" : {
        "total" : {
          "value" : 2,
          "relation" : "eq"
        },
        "max_score" : 0.5442147,
        "hits" : [
          {
            "_index" : "logs-2025.01",
            "_id" : "a1",
            "_score" : 0.5442147,
            "_source" : {
              "timestamp" : "2026-01-06T03:40:00Z",
              "level" : "ERROR",
              "message" : "connection timeout"
            }
          },
          {
            "_index" : "logs-2025.01",
            "_id" : "a2",
            "_score" : 0.41360325,
            "_source" : {
              "timestamp" : "2026-01-06T02:55:03Z",
              "level" : "WARN",
              "message" : "upstream timeout threshold reached"
            }
          }
        ]
      }
    }

    Remove pretty when scripting or measuring performance, since pretty-printing increases response size.

  4. Use a structured Query DSL search to combine full-text matching with exact filters and a time range.
    $ curl -s -H "Content-Type: application/json" -X POST "http://localhost:9200/logs-2025.01/_search?pretty" -d '{
      "query": {
        "bool": {
          "must": [
            { "match": { "message": "timeout" } }
          ],
          "filter": [
            { "term": { "level": "ERROR" } },
            { "range": { "timestamp": { "gte": "now-1d" } } }
          ]
        }
      }
    }'
    {
      "took" : 7,
      "timed_out" : false,
      "_shards" : {
        "total" : 1,
        "successful" : 1,
        "skipped" : 0,
        "failed" : 0
      },
      "hits" : {
        "total" : {
          "value" : 1,
          "relation" : "eq"
        },
        "max_score" : 0.5442147,
        "hits" : [
          {
            "_index" : "logs-2025.01",
            "_id" : "a1",
            "_score" : 0.5442147,
            "_source" : {
              "timestamp" : "2026-01-06T03:40:00Z",
              "level" : "ERROR",
              "message" : "connection timeout"
            }
          }
        ]
      }
    }

    Use term for exact matching on keyword fields and match for analyzed full-text fields.

  5. Sort results by timestamp in descending order with a small size limit.
    $ curl -s -H "Content-Type: application/json" -X POST "http://localhost:9200/logs-2025.01/_search?pretty" -d '{
      "size": 1,
      "sort": [
        { "timestamp": { "order": "desc" } }
      ],
      "query": { "match_all": {} }
    }'
    {
      "took" : 15,
      "timed_out" : false,
      "_shards" : {
        "total" : 1,
        "successful" : 1,
        "skipped" : 0,
        "failed" : 0
      },
      "hits" : {
        "total" : {
          "value" : 3,
          "relation" : "eq"
        },
        "max_score" : null,
        "hits" : [
          {
            "_index" : "logs-2025.01",
            "_id" : "a1",
            "_score" : null,
            "_source" : {
              "timestamp" : "2026-01-06T03:40:00Z",
              "level" : "ERROR",
              "message" : "connection timeout"
            },
            "sort" : [
              1767670800000
            ]
          }
        ]
      }
    }

    Sorting on a text field commonly fails with a fielddata error; sort on a keyword or date field instead (for example message.keyword or timestamp).

  6. Paginate to the next page using from with the same sort order.
    $ curl -s -H "Content-Type: application/json" -X POST "http://localhost:9200/logs-2025.01/_search?pretty" -d '{
      "from": 1,
      "size": 1,
      "sort": [
        { "timestamp": { "order": "desc" } }
      ],
      "query": { "match_all": {} }
    }'
    {
      "took" : 4,
      "timed_out" : false,
      "_shards" : {
        "total" : 1,
        "successful" : 1,
        "skipped" : 0,
        "failed" : 0
      },
      "hits" : {
        "total" : {
          "value" : 3,
          "relation" : "eq"
        },
        "max_score" : null,
        "hits" : [
          {
            "_index" : "logs-2025.01",
            "_id" : "a2",
            "_score" : null,
            "_source" : {
              "timestamp" : "2026-01-06T02:55:03Z",
              "level" : "WARN",
              "message" : "upstream timeout threshold reached"
            },
            "sort" : [
              1767668103000
            ]
          }
        ]
      }
    }

    Deep pagination with large from values is expensive; consider search_after for high-volume paging patterns.

  7. Confirm the total hit count for a query using filter_path to keep the response small.
    $ curl -s -H "Content-Type: application/json" -X POST "http://localhost:9200/logs-2025.01/_search?filter_path=took,hits.total&pretty" -d '{
      "query": { "match": { "message": "timeout" } }
    }'
    {
      "took" : 2,
      "hits" : {
        "total" : { "value" : 2, "relation" : "eq" }
      }
    }

    Large datasets may return relation as gte when totals are approximated; set track_total_hits for exact counts when needed.