Use Elasticsearch as a Prometheus data source in Grafana
Elasticsearch exposes a Prometheus-compatible HTTP API under the /_prometheus/ prefix.
Because the API follows the Prometheus query API, you can point Grafana's built-in Prometheus data source directly at Elasticsearch without sidecars, adapters, or pipeline changes.
Grafana then treats Elasticsearch like any other Prometheus backend: dashboard panels, query autocompletion, template variables, and Metrics Drilldown all use PromQL against metrics stored in Elasticsearch time series data streams (TSDS).
This guide covers connecting Grafana to Elasticsearch. To ingest metrics from Prometheus, see Prometheus remote write.
Before you build dashboards, review the PromQL limitations. Behavior differs from upstream Prometheus in a few areas, and some PromQL constructs are not evaluated yet. Knowing these up front helps you tell an expected limitation apart from a real problem when troubleshooting.
You need:
- The Elasticsearch endpoint for your deployment.
On Elasticsearch Serverless, find it in the Elastic Cloud console under Manage > Application endpoints, cluster and component IDs, next to Elasticsearch.
The endpoint looks like
https://<project-id>.es.<region>.<provider>.elastic.cloud. - An API key for Grafana to query metrics.
Create an API key scoped to read metrics data streams only, so a leaked Grafana key cannot be used to write data. In Control security privileges, use a role descriptor such as:
{
"query": {
"indices": [
{
"names": ["metrics-*.prometheus-*"],
"privileges": ["read", "view_index_metadata"],
"query": {
"terms": { "_tier": ["data_hot", "data_warm"] }
}
}
]
}
}
The metrics-*.prometheus-* pattern scopes the key to metrics ingested through Prometheus remote write, which is what this guide focuses on.
Because the key can read only those data streams, Grafana queries stay limited to that Prometheus data even when they use the default /_prometheus/ endpoint.
When the key is created, copy the Encoded value (select the Encoded format if Kibana offers a format dropdown). This is the value you pass directly as ApiKey <encoded>; you cannot retrieve it again after closing the dialog.
You can configure a separate Grafana data source per index pattern to give teams scoped access to their own metrics.
Add an index expression after /_prometheus/, for example https://<es_endpoint>/_prometheus/metrics-prod-*.
Index aliases are also supported as the index expression – you need to scope the API key to the alias name rather than the underlying index pattern.
See Index scoping.
Configure the data source through the Grafana UI or through provisioning.
- In Grafana, go to Connections → Data sources → Add data source, then choose Prometheus.
- Set the following:
- Name:
Elasticsearch - Prometheus server URL:
https://<es_endpoint>/_prometheus - Under Custom HTTP Headers, add a header:
- Header:
Authorization - Value:
ApiKey <query_api_key>
- Header:
- Name:
- Click Save & test.
Grafana confirms the connection, and autocompletion starts working in the query editor. The data source has more options than the ones above; for the authoritative steps and the full option reference, see the Grafana Prometheus data source documentation.
Grafana sends Prometheus queries with POST by default, which Elasticsearch accepts on authenticated HTTPS endpoints such as Elasticsearch Serverless.
If TLS terminates before Elasticsearch and the node receives plain HTTP, set the data source HTTP method to GET instead.
See Form-encoded POST requests.
To provision the data source from a file, create provisioning/datasources/datasource.yml, replacing <es_endpoint> and <query_api_key>:
apiVersion: 1
datasources:
- name: Elasticsearch
type: prometheus
access: proxy
url: "https://<es_endpoint>/_prometheus"
uid: elasticsearch-prometheus
isDefault: true
jsonData:
httpHeaderName1: Authorization
# If TLS terminates before Elasticsearch (plain HTTP to the node), uncomment:
# httpMethod: GET
secureJsonData:
httpHeaderValue1: "ApiKey <query_api_key>"
isDefault: true overrides the existing default data source for all users in the Grafana instance.
Set it to false if this deployment already has a default data source.
Mount the provisioning directory into Grafana at /etc/grafana/provisioning.
Existing Prometheus dashboards work against Elasticsearch without changes, as long as the PromQL they use is supported (see Limitations). You can point an existing dashboard at Elasticsearch or build a new one on top of the data source.
If a dashboard already runs against another Prometheus-compatible backend, switch its data source to Elasticsearch using the dashboard's data source variable dropdown (for dashboards that use one) or by editing the panels or the dashboard JSON model (for panels that reference a data source directly).
- Go to Dashboards → New → New dashboard → Add visualization.
- Select the Elasticsearch data source.
- In the query editor, write a PromQL query (for example,
sum(up)). Autocompletion and the metric browser are backed by the Elasticsearch metadata and discovery endpoints. - Choose a visualization, then save the dashboard.
When a panel shows an error, returns no data, or renders something unexpected, the cause is in one of two layers:
- The Elasticsearch Prometheus API (owned by Elastic).
- The Grafana Prometheus data source, which translates Grafana's requests into Prometheus API calls (owned by Grafana, not Elastic).
Grafana shows the data source's error message directly on the panel: hover or click the warning indicator in the panel's top-left corner. For most errors Elasticsearch returns a descriptive message, and you can act on it without reproducing anything:
- A message that a PromQL construct or query parameter is not supported yet points to a documented limitation. Adjust the query, or wait for the feature. No issue needed.
- An authentication or authorization message (
401/403) points to the API key. See Common symptoms. - A
406 Not Acceptableon every query points to the form-encodedPOSTrequirement. See Common symptoms.
If the message is clear and matches a documented limitation, you're done. If it is unclear, the panel returns wrong or empty data with no error, or you suspect the data source layer, continue to Step 2.
If the panel error is unclear, or the panel returns wrong or empty data with no error, open the query inspector (panel menu → Inspect → Query) and run the query.
The inspector shows both the request Grafana sent (endpoint, query, start, end, step) and the raw response from Elasticsearch, which usually points straight at the cause.
To confirm Elasticsearch's behavior independently of Grafana, replay the same request directly against the HTTP API.
Replace <es_endpoint> and <query_api_key> with your values:
curl "https://<es_endpoint>/_prometheus/api/v1/query_range" \
-H "Authorization: ApiKey <query_api_key>" \
--data-urlencode 'query=up' \
--data-urlencode 'start=2026-06-18T09:00:00Z' \
--data-urlencode 'end=2026-06-18T10:00:00Z' \
--data-urlencode 'step=15s'
For metric discovery, autocompletion, or template-variable problems, check the relevant metadata endpoint instead (for example /_prometheus/api/v1/labels or /_prometheus/api/v1/series).
Elasticsearch returns the expected data (in the inspector response or via curl), but the panel does not show it. The cause is in Grafana, not Elasticsearch, and is almost always configuration or usage rather than a bug: panel and visualization settings, template variables, the dashboard time range, or transformations. Review the panel and dashboard configuration. For help, use the Grafana documentation and community forums. Only open a Grafana issue if you can confirm an actual bug in the Prometheus data source.
Elasticsearch returns an error (
"status": "error") or wrong data. Before assuming an Elasticsearch problem, rule out a genuinely invalid query, which would fail against upstream Prometheus too. Confirm the expression is valid PromQL, for example with PromLens. Then check whether the behavior is an expected difference:- The expression might use a construct that Elasticsearch does not evaluate yet, such as set operators or group modifiers. These return a
4xxwitherrorType: bad_data. - The request might include a query parameter Elasticsearch does not support yet, which also fails with
4xx. - Instant queries and staleness handling differ from upstream Prometheus.
If the query is valid and the behavior is not explained by a documented limitation, open an Elasticsearch issue with the request and the full response body.
- The expression might use a construct that Elasticsearch does not evaluate yet, such as set operators or group modifiers. These return a
| Symptom | Likely cause | What to check |
|---|---|---|
406 Not Acceptable on every query |
Form-encoded POST is not accepted by this deployment |
Upgrade to the latest deployment version; ensure TLS terminates at Elasticsearch. See Form-encoded POST requests |
401/403 errors |
API key missing, invalid, or lacking privileges | Verify the Authorization: ApiKey <key> header and that the key grants read and view_index_metadata on metrics-*.prometheus-* |
| Empty results, no error | No matching data in the time range or index scope | Confirm metrics exist in the queried index scope and time window by reproducing the request directly |
bad_data error on a specific expression |
Unsupported PromQL construct | Compare the expression against unsupported constructs |
| Recurring errors related to exemplar queries | /api/v1/query_exemplars is not implemented |
In Grafana, go to Data sources → Elasticsearch → Exemplars and disable all configured exemplar links |
- HTTP API: the Prometheus-compatible endpoints Grafana calls.
- Limitations: unsupported PromQL constructs and HTTP behavior.
- Prometheus remote write: ingest metrics into Elasticsearch.