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 exists404 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
- Using GET instead of HEAD. GET returns the full mapping/settings body and is far heavier. Use HEAD for existence-only checks.
- Polling for existence on every application request. Cache the result; cluster state lookups add latency.
- Treating multi-index HEAD as "any of these".
HEAD /a,b,creturns 200 only if all named indices exist. - 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_aliasendpoints. - Race conditions between check and create. Two concurrent deploys can both pass the check. Either catch
resource_already_exists_exceptionon 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.
Related Reading
- Create an Index with Mapping: the operation existence checks are usually gating.
- Delete an Index in Elasticsearch: the inverse operation.
- List Indices: when you want to enumerate, not check a single name.
- Index Aliases: why a HEAD on a name might be matching an alias.
- Update Index Settings: a typical follow-up call after confirming an index exists.
- Reindex Data Guide: existence checks frequently appear in migration scripts.