How to perform a search query in Elasticsearch

Searching existing data in Elasticsearch is how operators confirm ingestion, isolate failures, and answer application questions without pulling raw documents back out of the cluster. A good search flow starts by targeting the correct index, alias, or data stream and then matching the query type to the mapped field types.

The Search API accepts a quick Lucene query-string search through the q parameter and full JSON request bodies through the Query DSL. Current Elastic docs note that q is useful for fast ad hoc checks but does not support the full Query DSL, and if a request sends both q and a JSON query body, the URI query-string search takes precedence.

Search behavior also depends on mappings. Analyzed text fields work best with match queries, while exact filters and repeatable sorting usually rely on keyword, date, or numeric fields. Current self-managed deployments typically use an authenticated HTTPS endpoint for these API calls.

Steps to perform a search query in Elasticsearch:

  1. List the available indices to confirm the search target name.
    $ curl -sS --fail "http://localhost:9200/_cat/indices?v&s=index&h=health,status,index,docs.count,store.size"
    health status index                     docs.count store.size
    green  open   app-events-search-2026.04          4     17.6kb
    green  open   app-metrics-2026.04                2      8.2kb

    If the data lives in a data stream or alias, search that logical name instead of a hidden backing index so future rollovers do not break the query target.

  2. Inspect the mapping before building filters or sort clauses.
    $ curl -sS --fail "http://localhost:9200/app-events-search-2026.04/_mapping?pretty&filter_path=*.mappings.properties"
    {
      "app-events-search-2026.04" : {
        "mappings" : {
          "properties" : {
            "event_id" : {
              "type" : "keyword"
            },
            "level" : {
              "type" : "keyword"
            },
            "message" : {
              "type" : "text",
              "fields" : {
                "keyword" : {
                  "type" : "keyword",
                  "ignore_above" : 256
                }
              }
            },
            "timestamp" : {
              "type" : "date"
            }
          }
        }
      }
    }

    Current Elastic docs still position match as the standard full-text query and term as the exact-value query for keyword fields. Sorting and exact filtering commonly use message.keyword, level, or another field with doc values.

  3. Run a quick URI search with q when you need a fast ad hoc check.
    $ curl -sS --fail -G "http://localhost:9200/app-events-search-2026.04/_search?pretty&filter_path=took,hits.total,hits.hits._id,hits.hits._source" --data-urlencode "q=timeout"
    {
      "took" : 4,
      "hits" : {
        "total" : {
          "value" : 3,
          "relation" : "eq"
        },
        "hits" : [
          {
            "_id" : "evt-1004",
            "_source" : {
              "timestamp" : "2026-04-02T06:19:44Z",
              "level" : "ERROR",
              "message" : "database timeout while writing session"
            }
          },
          {
            "_id" : "evt-1002",
            "_source" : {
              "timestamp" : "2026-04-02T06:16:52Z",
              "level" : "WARN",
              "message" : "upstream timeout threshold reached"
            }
          }
        ]
      }
    }

    Use --data-urlencode so spaces and special characters in the query are escaped safely.

    Current Elastic API docs note that the q parameter uses Lucene query-string syntax, does not support the full Query DSL, and overrides any JSON query body sent with the same request.

  4. Use a structured Query DSL request when the search needs full-text matching plus exact filters and sorting.
    $ curl -sS --fail -H "Content-Type: application/json" -X POST "http://localhost:9200/app-events-search-2026.04/_search?pretty&filter_path=took,hits.total,hits.hits._id,hits.hits._source" -d '{
      "size": 2,
      "_source": ["timestamp", "level", "message"],
      "sort": [
        { "timestamp": { "order": "desc" } }
      ],
      "query": {
        "bool": {
          "must": [
            { "match": { "message": "timeout" } }
          ],
          "filter": [
            { "term": { "level": "ERROR" } },
            { "range": { "timestamp": { "gte": "now-24h" } } }
          ]
        }
      }
    }'
    {
      "took" : 3,
      "hits" : {
        "total" : {
          "value" : 2,
          "relation" : "eq"
        },
        "hits" : [
          {
            "_id" : "evt-1004",
            "_source" : {
              "timestamp" : "2026-04-02T06:19:44Z",
              "level" : "ERROR",
              "message" : "database timeout while writing session"
            }
          },
          {
            "_id" : "evt-1001",
            "_source" : {
              "timestamp" : "2026-04-02T06:15:00Z",
              "level" : "ERROR",
              "message" : "connection timeout while opening upstream socket"
            }
          }
        ]
      }
    }

    The match query is the standard full-text query in current Elastic docs. Keep term filters on exact-value fields such as keyword, date, or numeric fields rather than analyzed text fields.

  5. Page through a small result set with the same sort order every time.
    $ curl -sS --fail -H "Content-Type: application/json" -X POST "http://localhost:9200/app-events-search-2026.04/_search?pretty&filter_path=hits.total,hits.hits._id,hits.hits._source" -d '{
      "from": 1,
      "size": 1,
      "sort": [
        { "timestamp": { "order": "desc" } }
      ],
      "query": {
        "match": { "message": "timeout" }
      }
    }'
    {
      "hits" : {
        "total" : {
          "value" : 3,
          "relation" : "eq"
        },
        "hits" : [
          {
            "_id" : "evt-1002",
            "_source" : {
              "timestamp" : "2026-04-02T06:16:52Z",
              "level" : "WARN",
              "message" : "upstream timeout threshold reached"
            }
          }
        ]
      }
    }

    Current Elastic docs keep the same rule: from and size are fine for shallow paging, but the default index.max_result_window limit still blocks paging past 10,000 hits. Use search_after with a fixed sort key for deep pagination.

  6. Request an exact hit count when dashboards or follow-up automation must know whether the match set is complete.
    $ curl -sS --fail -H "Content-Type: application/json" -X POST "http://localhost:9200/app-events-search-2026.04/_search?pretty&filter_path=took,hits.total" -d '{
      "track_total_hits": true,
      "query": {
        "match": {
          "message": "timeout"
        }
      }
    }'
    {
      "took" : 2,
      "hits" : {
        "total" : {
          "value" : 3,
          "relation" : "eq"
        }
      }
    }

    Elastic's current API reference notes that track_total_hits returns the exact hit count at extra cost. Leave it off when a fast top-N query is enough, and use filter_path or _source filtering to keep routine responses small.