How to Check if an Index Exists in Elasticsearch

To check if an index exists in Elasticsearch, send HEAD /<index_name>. The API returns 200 if the index exists and 404 if it does not, with no response body. Every official client wraps this as a single boolean call. Use it before creating an index in deployment scripts, before running operations that 404 on missing indices, and during data-migration health checks.

The Quick Answer: HEAD /index

HEAD /my-index

Response is HTTP status only:

  • 200 OK - the index exists
  • 404 Not Found - the index does not exist

With curl:

curl -I -X HEAD "http://localhost:9200/my-index"

The -I flag prints only the response headers, which is what you want for a HEAD probe. With authentication:

curl -I -X HEAD -u "$ES_USER:$ES_PASS" "https://localhost:9200/my-index"

Checking Multiple Indices

HEAD accepts a comma list:

HEAD /index-a,index-b,index-c

This returns 200 only if every named index exists. A single missing index produces 404. If you need per-index results, query them individually or fetch metadata:

GET /index-a,index-b,index-c/_settings?ignore_unavailable=true

Indices missing from the response do not exist; the rest are returned with their settings. The ignore_unavailable=true flag prevents a 404 on the missing names.

Checking Aliases vs Indices

HEAD /<name> matches both indices and aliases. To check specifically for an alias:

HEAD /_alias/my-alias

Returns 200 if the alias exists, 404 otherwise.

To check if an index has a particular alias:

HEAD /my-index/_alias/my-alias

Checking from Client Libraries

Python (elasticsearch-py)

if not es.indices.exists(index="my-index"):
    es.indices.create(index="my-index", mappings={ ... })

indices.exists() returns a boolean directly.

JavaScript (@elastic/elasticsearch)

const exists = await client.indices.exists({ index: 'my-index' })
if (!exists) {
  await client.indices.create({ index: 'my-index', mappings: { ... } })
}

Java REST client

boolean exists = client.indices().exists(e -> e.index("my-index")).value();

Go (elastic/go-elasticsearch)

res, _ := es.Indices.Exists([]string{"my-index"})
exists := res.StatusCode == 200

curl + jq for shell scripts

status=$(curl -s -o /dev/null -w '%{http_code}' -I "$ES/my-index")
if [ "$status" = "200" ]; then
  echo "exists"
fi

The Idempotent Create Pattern

The point of checking existence is usually to make a create-or-skip script idempotent. There are two good patterns:

Pattern 1: HEAD then create

if not es.indices.exists(index="my-index"):
    es.indices.create(index="my-index", mappings={ ... })

Race condition: two scripts running concurrently can both pass the check and one will fail at create time with resource_already_exists_exception. Catch the exception.

Pattern 2: Always create, ignore 400

from elasticsearch.exceptions import RequestError
try:
    es.indices.create(index="my-index", mappings={ ... })
except RequestError as e:
    if e.error != 'resource_already_exists_exception':
        raise

Pattern 2 is simpler and race-safe. Use it for production deploy scripts.

Operating Existence Checks in Production

Existence checks are cheap individually but expensive in aggregate when application code does them on every request. Each HEAD /<index> reads cluster state; on a cluster with tens of thousands of indices, the cluster-state lookup is not free, and high-rate existence polling can put pressure on the master node. Cache the result in your application and refresh on a longer interval, not per-request.

The deeper diagnostic question - "what indices do I actually have, and which ones should I clean up?" - is rarely answered by ad-hoc existence checks. Pulse maintains a continuous inventory of every index in the cluster, with size, health, ILM coverage, and last-write time, so the question shifts from "does this index exist?" to "should it?". When deploy scripts start failing or applications race to create the same index, Pulse's auto-RCA shows the creation history and conflicting writers.

Common Mistakes

  1. Using GET instead of HEAD. GET returns the full mapping/settings body and is far heavier. Use HEAD for existence-only checks.
  2. Polling for existence on every application request. Cache the result; cluster state lookups add latency.
  3. Treating multi-index HEAD as "any of these". HEAD /a,b,c returns 200 only if all named indices exist.
  4. Forgetting that aliases match the same API. HEAD /<name> returns 200 for either an index or an alias named <name>. If you need to distinguish, use _alias endpoints.
  5. Race conditions between check and create. Two concurrent deploys can both pass the check. Either catch resource_already_exists_exception on create, or use a distributed lock.

Frequently Asked Questions

Q: What HTTP status code does Elasticsearch return when an index does not exist?
A: HEAD /<index> returns 404 Not Found when the index does not exist, and 200 OK when it does. The response body is empty - the status code is the answer.

Q: How do I check if an index exists in Python with elasticsearch-py?
A: Use es.indices.exists(index="my-index"), which returns a boolean. Wrap it in an if not to make a create-or-skip block. Catch RequestError with error == 'resource_already_exists_exception' to handle the race when two scripts run concurrently.

Q: Can I check multiple indices at once?
A: Yes. Pass a comma-separated list (HEAD /a,b,c) or a wildcard pattern (HEAD /logs-*). The call returns 200 only if every named index exists; one missing name produces 404. For per-index results, call _settings with ignore_unavailable=true.

Q: What is the difference between HEAD and GET for checking index existence?
A: HEAD /<index> returns only the HTTP status. GET /<index> returns the full settings, mappings, and aliases body, which is much heavier on cluster state and network. Always use HEAD when you only need a yes/no answer.

Q: How can I check whether an alias exists separately from an index?
A: Use HEAD /_alias/<alias-name> to check just the alias, or HEAD /<index>/_alias/<alias-name> to check whether a specific index has a specific alias. A plain HEAD /<name> does not distinguish - it matches either.

Q: Is there a performance cost to checking index existence frequently?
A: Individual calls are cheap but read cluster state, which is shared with every node. On large clusters or under high call rates, this can put pressure on the master node. Cache the result on the application side and refresh on a slow interval.

Q: How do I make a deployment script idempotent without races?
A: Skip the existence check and always issue a create. Catch resource_already_exists_exception and treat it as success. This is race-free and one fewer round trip than the check-then-create pattern.

Subscribe to the Pulse Newsletter

Get early access to new Pulse features, insightful blogs & exclusive events , webinars, and workshops.

We use cookies to provide an optimized user experience and understand our traffic. To learn more, read our use of cookies; otherwise, please choose 'Accept Cookies' to continue using our website.