ObservabilityXXV · Grafana Data SourcesGrafanaDataSources
Prometheus as a Data Source
What you'll learn
- Provision a Prometheus data source in Grafana 11.x with explicit URL, auth and TLS settings
- Explain how the data-source proxy terminates the request path from the browser to the Prometheus HTTP API
- Read the `/api/datasources/uid/<uid>/health` response and recognise the four health states
- Distinguish a Prometheus scrape timeout from a Grafana query timeout and align them deliberately
- Diagnose the common production failure modes: 401, TLS handshake, label-name drift, and scrape interval drift
Prerequisites
Verified against Prometheus 2.55.x · Alertmanager 0.28.x · node_exporter 1.8.x · blackbox_exporter 0.26.x · Grafana 11.x · Loki 3.x · Tempo current · OpenTelemetry Collector 0.110.x · Grafana Alloy current · Docker Engine 28.x · Ubuntu 24.04 LTS · Debian 12 (Bookworm) · RHEL / Rocky / AlmaLinux 9.x · 2026-08-13
A panel goes red at 03:00. The dashboard says “No data.”
The on-call engineer opens the Explore view in Grafana, types
up into the Prometheus query editor, and gets a yellow banner:
Network Error: 502 Bad Gateway
datasource: prom-prod-eu, uid: prom-prod-eu
The datasource is configured. The query is the simplest query in PromQL. The data source health check — the very check Grafana runs before it sends a panel query — already says the connection is unreachable. The lesson this time is about that connection: the YAML that declares it, the data-source proxy that mediates it, the health check that audits it, and the timeout interaction that makes a “working” Prometheus silently lie about whether it is working.
What it is
A Prometheus data source in Grafana is a named, configured
client of the Prometheus HTTP API. The provisioning file
declares the data source’s URL, type, credentials, TLS posture,
and a small set of behavioural options. Once provisioned, Grafana
runs every panel query through a server-side proxy at
/api/datasources/proxy/uid/<uid>/..., never directly from the
browser. The proxy is the only component that holds the
credentials.
In Grafana 11.x the canonical Prometheus data source type is
prometheus. The legacy alias Prometheus (capitalised) still
appears in older dashboards and is resolved identically at query
time; new provisioning should use the lowercase form. The
url value points at the Prometheus /api/v1 endpoint, not
at the metrics root.
Why a sysadmin cares
The Prometheus data source is the most-used data source in any metrics stack and the one whose silent failures cost the most time. Three production shapes appear repeatedly:
- The credential rotates upstream and Grafana does not know.
Prometheus’ basic-auth password was changed in the secret
manager. Grafana still holds the old
secureJsonData.password. Every panel renders401 Unauthorizedand the dashboards are treated as “broken” by the on-call. The fix is not a restart; it is a redeploy with a newsecureJsonDatavalue. - The TLS posture changes. A Prometheus instance behind a
sidecar gets a new certificate, or its CA bundle rotates.
Grafana’s
tlsConfigstill pins the old CA. Panels either fail the handshake silently (Grafana reports “no data”) or surface anx509: certificate signed by unknown authorityerror in the data-source health check. - Scrape interval drift. The Grafana query timeout defaults
to 60 seconds. The Prometheus scrape interval is 15 seconds.
A panel that asks for a six-hour window with a 1-second step
sends a query that Prometheus cannot answer inside 60 seconds.
Grafana reports
context deadline exceededand the on-call concludes Prometheus is “broken” — when Prometheus is, in fact, working correctly and the panel is the wrong shape.
How it works: the request path
browser grafana-server prometheus
------- -------------- ----------
| | |
|--Panel query-->| |
| |--GET /api/v1/query_range|
| | (BasicAuth, TLS, ...) |
| | |
| |<--200 OK + samples-----|
|<--Panel data---| |
Three observations:
- The browser never sees the credentials. Every panel query
from the browser is rewritten by Grafana into a server-side
call to
/api/datasources/proxy/uid/<uid>/api/v1/query_range. The credentials live insecureJsonDataon the server. - The data source is identified by UID, not by name. A
panel that says “Prometheus” by name silently breaks the
moment the data source is renamed; a panel that says
prom-prod-euby UID keeps working through renames. - The HTTP path through the proxy mirrors the upstream path.
/api/v1/query,/api/v1/query_range,/api/v1/series,/api/v1/labels,/api/v1/metadata— Grafana proxies them all. A query against an unknown path returns 404 from the proxy and the same 404 from Prometheus; the round trip is not free.
How to configure it
# /etc/grafana/provisioning/datasources/prometheus.yml
apiVersion: 1
datasources:
- name: prom-prod-eu
uid: prom-prod-eu # stable; never reuse across instances
type: prometheus
access: proxy # Grafana proxies; the browser never sees creds
orgId: 1
url: https://prom-prod-eu.internal:9090
isDefault: true
editable: false # prevent click-ops from drifting from Git
# Basic auth. Username in jsonData, password in secureJsonData.
basicAuth: true
basicAuthUser: grafana-reader
jsonData:
tlsAuth: false
tlsAuthWithCACert: true
tlsSkipVerify: false
# Query timeout: 60s matches the Grafana default.
# Set below the Prometheus --query.timeout (default 2m).
timeInterval: 15s # the step Grafana assumes for "last X" queries
httpMethod: POST # large query_range bodies exceed GET limits
secureJsonData:
tlsCACert: |
-----BEGIN CERTIFICATE-----
MIIDazCCAlOgAwIBAgIUJx...
-----END CERTIFICATE-----
basicAuthPassword: ${PROM_PASSWORD} # injected by the provisioner
A few production notes on the options:
access: proxyis the right default. Thedirectmode sends credentials to the browser; only use it for unauthenticated, loopback-only Prometheis.uidis the stable identifier. Use a name that describes the environment and region, not the URL.isDefaultmakes this the implicit target of new panels. Setting it on more than one Prometheus per Grafana instance produces surprising behaviour when an Explore query has no explicit target.timeIntervalis the step Grafana uses when the user picks “Last 6 hours” without specifying a step. Setting it lower than the Prometheus scrape interval produces “duplicate sample timestamp” warnings.httpMethod: POSTis required when the query or the label selector exceeds a few hundred characters; the default GET caps at 8 KB on most proxies.tlsAuthWithCACertplus the CA cert insecureJsonDatais the right posture for a Prometheus with a private CA. Skipping verification (tlsSkipVerify: true) is acceptable only for development.
How to validate it
# READ-ONLY: the data source is provisioned and reachable.
curl -fsS -u grafana-admin:$GRAFANA_ADMIN \
http://grafana.internal:3000/api/datasources/uid/prom-prod-eu
# {
# "id": 1,
# "uid": "prom-prod-eu",
# "name": "prom-prod-eu",
# "type": "prometheus",
# "url": "https://prom-prod-eu.internal:9090",
# "access": "proxy",
# "isDefault": true,
# ...
# }
# READ-ONLY: the health check returns one of four states.
curl -fsS -u grafana-admin:$GRAFANA_ADMIN \
http://grafana.internal:3000/api/datasources/uid/prom-prod-eu/health
# {"message":"Data source is working","status":"success"}
# READ-ONLY: a minimal query against the proxy.
curl -fsS -u grafana-admin:$GRAFANA_ADMIN \
--data-urlencode 'query=up' \
--data-urlencode 'time=2026-08-13T10:00:00Z' \
http://grafana.internal:3000/api/datasources/proxy/uid/prom-prod-eu/api/v1/query
# {"status":"success","data":{"resultType":"vector","result":[...]}}
# READ-ONLY: Prometheus-side query log to confirm the request landed.
ssh prom-host 'tail -n 5 /var/log/prometheus/query.log'
# ts=2026-08-13T10:00:00 caller=queryable.go:... component=tsdb
# method=query endpoint=/api/v1/query ...
# CONFIGURATION: reload Grafana provisioning.
sudo systemctl reload grafana-server
A clean validation: the uid is present, the health check
returns "status": "success", the proxy returns Prometheus
data, and the Prometheus query log shows the Grafana query.
Anything less is a symptom; each failure mode below maps to one
of these four signals failing.
How it can fail
The most expensive Prometheus data source failure modes from real production incidents.
- Stale
basicAuthPassword. The credential rotated in the secret store; Grafana still holds the previous value. The health check returns401 Unauthorized, every panel showsNetwork Error, and the on-call concludes Prometheus is down. The symptom isstatus: 401in/api/datasources/uid/<uid>/health. - CA bundle drift. The private CA that signs Prometheus’
certificate rotated. Grafana’s
tlsCACertis stale. The health check returns the samesuccessshell with ax509: certificate signed by unknown authorityerror in the underlying message. The symptom is the health-checkmessagefield containing the cert error string. - Proxy URL points at the metrics root, not at
/api/v1. Theurlvalue ishttps://prom.internal:9090rather than the explicit API endpoint. The health check sometimes succeeds (Prometheus returns its own/-rooted page) and every panel fails withparse error. The symptom is panels returningparse error: unexpected EOFwhile/api/datasources/.../healthis green. - Scrape interval / step mismatch. Prometheus scrapes every
15 seconds. Grafana’s
timeIntervalis 5 seconds. The query engine asks for 1-second steps and Prometheus returns duplicate timestamps. The symptom iserror: duplicate sample timestampwarnings at every panel load. - HTTP method left at GET. A panel with a 12 KB label selector (large cardinality, deep service-name regex) hits the GET URL-length limit and is rejected by the proxy before it ever reaches Prometheus. The symptom is intermittent panel failures correlated with selector length.
- DNS split-horizon. The data source URL is
prom.internalbut the Grafana host resolves it via a different DNS path than the application hosts. Health checks pass from Grafana’s perspective but the upstream Prometheus reports the query against a different replica. The symptom is “data is correct but timestamps lag the rest of the platform by 30 seconds”.
How to troubleshoot it
The diagnostic order is “is the data source provisioned?”, “is the proxy reachable?”, “is the upstream responding?”, “is the credential right?”.
- Confirm the data source exists.
GET /api/datasources/uid/<uid>. If 404, the provisioning file did not reload; checkgrafana.logfor parse errors. - Confirm the health check.
GET /api/datasources/uid/<uid>/health. The four states aresuccess,error,notfound, and the rarely-seenforbidden. Each maps to a different upstream condition. - Reproduce the request through the proxy.
curlagainst/api/datasources/proxy/uid/<uid>/api/v1/query?query=up. This is the exact request Grafana issues for the simplest panel. If this fails, the failure is in the proxy chain. - Reproduce the request directly.
curlagainst the upstream Prometheus URL with the same credentials Grafana uses. If this succeeds and the proxy call fails, the failure is in Grafana’ssecureJsonDataortlsConfig. - Inspect Grafana’s logs.
/var/log/grafana/grafana.logrecords every proxy call and its outcome, with the UID and the upstream status. A 401 from Prometheus appears as a single line withstatus=401; a TLS error appears with thex509string. - Cross-check the credential in the secret store. A
vault reador equivalent that confirms the value Grafana is using matches the value Prometheus expects.
Security implications
- The proxy holds the credentials. A Grafana with a
misconfigured
access: directwould expose the password to every browser. The fix isaccess: proxyand a server-side network ACL that limits/api/datasources/.../secure/*to service-account tokens. - The CA cert is a credential too. A Grafana that pins the wrong CA cannot distinguish a legitimate Prometheus from a network attacker with a self-signed cert. Verify the chain out-of-band at every rotation.
- The
secureJsonDatapayload lives in memory. A Grafana process dump or approfheap profile can leak the password. Disable the debug endpoints on production Grafana ([security] disable_gravatar = trueis unrelated, but[users] allow_sign_up = falseand the[analytics]-related public endpoints should be off). - The health check leaks the upstream status. A 401 from
Prometheus is visible to any Grafana admin. Treat the
/api/datasources/uid/<uid>/healthendpoint as authenticated-and-internal-only.
Performance implications
- Query latency is on the proxy. The path is browser → Grafana server → Prometheus. The Grafana server hop is non-negotiable for credentialed sources. Place Grafana and Prometheus in the same availability zone to keep the additional hop under 1 ms.
- Query timeout is on Grafana. Default 60 seconds. Set it
below the Prometheus
--query.timeoutso Grafana fails fast on slow queries and Prometheus does not accumulate abandoned work. httpMethod: POSTremoves the GET URL-length cap. A panel with a 12 KB selector is rejected by GET; POST is required at scale.scrape_intervalandtimeIntervalshould match. A Prometheus with a 15-second scrape and a Grafana with a 5-secondtimeIntervalproducesduplicate sample timestampwarnings at every panel render.
Production guidance
- Pin the
uid. Renaming a data source is fine; changing the UID breaks every panel that references it. - Use
access: proxyfor every Prometheus with credentials. - Align
timeIntervalto the scrape interval. Document both in the team’s instrumentation guide. - Reload Grafana provisioning through the GitOps pipeline, not by click-ops in the UI.
- Verify the health check from the alerting path. A Grafana
that flips a Prometheus to
errorshould page the on-call before a panel renders red. - Test credential rotation in staging. The cost of a “broken” production Grafana at the moment of a planned credential rotation is a real and recurring outage shape.
Verification
You should now be able to answer:
- What is the operational difference between
access: proxyandaccess: directfor a Prometheus data source in Grafana? - What does the
GET /api/datasources/uid/<uid>/healthendpoint actually verify, and what does it not verify? - How does Grafana’s query timeout interact with Prometheus’
--query.timeout, and which one should be smaller? - Why does a “Data source is working” health check sometimes coexist with broken panels?
Quiz
Knowledge check · 8 questions
Q1. What is the role of the Grafana data-source proxy for a Prometheus source with basic auth?
Q2. Setting `access: direct` for a credentialed Prometheus data source is an acceptable production default.
Q3. The Grafana query timeout is 60 seconds and the Prometheus --query.timeout flag is unset (default 2 minutes). A panel that asks for a long-range query returns `context deadline exceeded`. Which side should be tightened?
Q4. Which of these are valid signals from `GET /api/datasources/uid/<uid>/health` on Grafana 11.x?
Q5. A panel returns `duplicate sample timestamp` warnings. The Prometheus scrape interval is 15s and the Grafana `timeInterval` is 5s. What is the fix?
Q6. Name the Grafana 11.x API endpoint that returns the health status of a Prometheus data source identified by its UID.
Q7. Why does `httpMethod: POST` matter for a high-cardinality Prometheus data source?
Q8. A green `Data source is working` health check is sufficient evidence that panel queries against the same data source will succeed.
Passing score: 75%. Answers are checked in this browser.