ObservabilityXXIII · Grafana FoundationsGrafanaFoundations
Datasources
What you'll learn
- Configure a Prometheus, Loki, and Tempo data source in YAML so the configuration is reproducible
- Distinguish plain JSON fields from secure JSON fields and store the latter in a secrets manager
- Read /api/datasources and /api/datasources/uid/<uid>/health to verify a data source is reachable
- Diagnose the most common data source failures: wrong UID, wrong URL, missing auth, missing CORS
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 dashboard template assumes a Prometheus data source called
“Prometheus”. The new cluster uses the UID “prom-prod”. The panel
query up{instance=~"web.*"} returns “datasource not found”. The
on-call engineer spends twenty minutes finding out that two UIDs
were changed in different pull requests.
This lesson is about that misnaming. A data source is a tiny object in the storage layer (URL, type, UID, credentials, options) plus a plugin that knows how to talk to it. Getting the UID, the URL, and the credentials right is the entire job.
What a data source is
A data source is a Grafana-managed connection to a backend that
can answer queries. Each data source has a type — a backend kind
such as prometheus, loki, tempo, mysql, or postgres —
and a plugin that knows that backend’s wire protocol and query
language. The same panel can target any data source whose plugin
s the panel’s expected data shape.
Grafana 11 ships the following plugins enabled by default in the official container image:
- Prometheus, Loki, Tempo, Mimir (Grafana Cloud-compatible)
- InfluxDB (v1 + v2), Elasticsearch, OpenSearch, MySQL, MSSQL, Postgres, ClickHouse
- TestData (useful for development; never use in production)
- Alertmanager (legacy; replaced by Grafana Alerting for new installations)
- Zipkin, Jaeger, Pyroscope (legacy tracing)
Enterprise distributions and the community plugin catalogue add hundreds more. The cost of catalogue breadth is that the plugin quality varies; keep your production list small.
Why a sysadmin cares
A misconfigured data source is the difference between a dashboard that answers a question and a dashboard that displays the red triangle. Three things go wrong in production:
- Wrong URL. The data source points at a Prometheus that exists but is on the wrong port, or at a Prometheus that was rebuilt with a different DNS name.
- Wrong UID. A developer renames the data source UID in one dashboard but not the others. The dependent panels fail with “datasource not found”.
- Wrong credentials. The read token expired, the basic-auth user was rotated, or the secure-JSON field was overwritten with an empty value.
Each of these is fixable in minutes once the operator knows which field is wrong. The lesson is the field name and the lookup path.
How it works
The data-source plugin runs in-process inside the Grafana server.
The query goes from the panel, to the plugin’s QueryData method,
to the backend, and back as a data frame (a columnar, typed
result). The data source’s proxy is what makes the Grafana server
a useful intermediary: the browser calls Grafana, Grafana calls the
backend with the correct credentials, and the browser never sees the
secret.
+----------------+ query +-----------+ HTTPS +--------------+
| Browser panel | ---------------> | Grafana | -------------------> | Prometheus |
| (no credentials) | <--------------- | server | <-------------------- | Loki / Tempo |
+----------------+ data frame +-----------+ data frame +--------------+
^ | ^
| | |
plugin runtime secureJsonData |
| basic auth /
| bearer token
v
secrets manager /
keyring file
Three properties of this diagram matter operationally:
- The browser never holds a data source credential. The
proxy is the only thing that does. This is why the
secureJsonData.passwordfield exists and why passwords do not appear in/api/datasourcesresponses. - The UID is the stable handle. Dashboards reference the UID; renaming the name does not break the dashboard but renaming the UID does. UIDs are alphanumeric, lowercase, and unchanged by the UI’s “rename” action on the data source.
- The HTTP routing is by
namein the URL. TheGET /api/datasourcesproxy endpoint is at/api/datasources/<uid>/proxy/<path>. Clients normally should not need this; dashboard panels route through the plugin, not the proxy.
How to configure it
File-based provisioning is the only configuration that survives a
container restart and the only one that belongs in version control.
The directory /etc/grafana/provisioning/datasources/ holds one
YAML per data-source kind.
# /etc/grafana/provisioning/datasources/metrics.yaml
apiVersion: 1
datasources:
- name: Prometheus
uid: prom-prod
type: prometheus
access: proxy
orgId: 1
url: http://prometheus.monitoring.svc:9090
isDefault: true
version: 1
editable: false
jsonData:
httpMethod: POST
timeInterval: 30s
queryTimeout: 60s
manageAlerts: true
prometheusType: Prometheus
prometheusVersion: 2.55.0
secureJsonData:
basicAuthPassword: ${PROM_READ_TOKEN}
# /etc/grafana/provisioning/datasources/logs.yaml
apiVersion: 1
datasources:
- name: Loki
uid: loki-prod
type: loki
access: proxy
url: http://loki.monitoring.svc:3100
editable: false
jsonData:
maxLines: 1000
timeout: 60
httpMethod: GET
secureJsonData:
basicAuthPassword: ${LOKI_READ_TOKEN}
# /etc/grafana/provisioning/datasources/traces.yaml
apiVersion: 1
datasources:
- name: Tempo
uid: tempo-prod
type: tempo
access: proxy
url: http://tempo.monitoring.svc:3200
editable: false
jsonData:
httpMethod: GET
tracesToLogsV2:
datasourceUid: loki-prod
tags: [job, instance]
spanStartTimeShift: -1s
spanEndTimeShift: 1s
serviceMap:
datasourceUid: prom-prod
nodeGraph:
enabled: true
secureJsonData: {}
editable: false is the configuration call an operator cares
about. It prevents a developer from saving a one-off data source
through the UI that then quietly overrides the file-based one on
the next provisioning poll.
Environment variables in secureJsonData are expanded by
Grafana at load time; they are not stored as plain text. The
syntax ${VAR} reads from the process environment. The
expression ${VAR:-default} is supported and is the form used
for non-secret defaults in jsonData.
How to validate it
Three checks confirm a data source is up from the operator’s view.
Severity: READ-ONLY.
# 1. List every data source Grafana knows about.
curl -sf -u "grafana-admin:$GF_ADMIN_PASSWORD" \
http://grafana:3000/api/datasources | jq '.[] | {uid, type, url}'
{
"uid": "prom-prod",
"type": "prometheus",
"url": "http://prometheus.monitoring.svc:9090"
}
{
"uid": "loki-prod",
"type": "loki",
"url": "http://loki.monitoring.svc:3100"
}
{
"uid": "tempo-prod",
"type": "tempo",
"url": "http://tempo.monitoring.svc:3200"
}
# 2. Run the data source health check.
curl -sf -u "grafana-admin:$GF_ADMIN_PASSWORD" \
http://grafana:3000/api/datasources/uid/prom-prod/health | jq
{
"message": "Data source is working",
"status": "success"
}
# 3. From a Grafana host, confirm the backend is reachable on
# its own protocol. For Prometheus:
curl -sf -o /dev/null -w "%{http_code}\n" \
http://prometheus.monitoring.svc:9090/-/ready
# 200
A 200 from /api/datasources/uid/<uid>/health proves Grafana
can talk to the backend. A 200 from the backend’s own health
endpoint proves the backend is healthy. The two together prove
the data path the panel will take is open.
How it can fail
Five high-frequency failure shapes:
- UID collision. Two data sources share the same UID — the
second is rejected at provisioning time. Symptom: log line
data source with uid <x> existsand the missing source in/api/datasources. - Wrong URL.
urlpoints at a host or port that exists but is the wrong service — for example, a Loki URL ending in:9090(Prometheus). Symptom:/healthreturns an unparseable response, panels display “Unexpected error”,/api/datasources/uid/<uid>/healthreturns"status": "error"with a 200 response. - Missing credentials. The backend requires basic auth and
the password is unset or expired. Symptom:
healthreturns"status": "error"with HTTP401propagated from the backend. The Grafana log includes401 Unauthorizedfrom the plugin. - CORS or direct-access mismatch. The panel uses a backend
that the browser should reach directly, but the backend has
no CORS headers. Symptom: the browser’s network tab shows a
blocked request; Grafana’s
/healthis fine. The fix is to setaccess: proxyrather thandirect. secureJsonDatawiped by env var expansion. The${PROM_READ_TOKEN}is unset in the container environment, so Grafana interprets it as the empty string and stores an empty password. Symptom:/healthreturns"status": "error";logs/grafana/grafana.logdoes not log the secret (correctly) but does logUsing empty value for $\{...\}at startup (after the relevant config reload).- Plugin removed after upgrade. A plugin was uninstalled
or is incompatible with the new Grafana version. Symptom:
datasourceslisting skips the entry; the provisioned source is dropped on every reload.
How to troubleshoot it
The order works because each step eliminates a layer.
- Does Grafana see the source?
/api/datasources. Missing means provisioning failed; check the Grafana log for YAML errors. - Does the plugin pass?
/api/datasources/uid/<uid>/health.status: errorhere isolates the failure to the network or credentials, not to the JSON configuration. - Is the backend reachable?
curl http://.../-/ready(or/readyfor Loki,/status/versionfor Tempo).5xxor connection refused means the data source is fine; the backend is down. - Are the credentials correct? Tail Grafana’s logs for the
401 / 403line. Verify with a manualcurl -u user:pass https://.... - Is the plugin version compatible with Grafana?
grafana cli plugins lsand checkversion. Plugin manifest requires the runtime version number to be in itsdependencies.grafanaDependencyrange. - Is the datasource proxy route blocked? From the Grafana
process,
tcp <host> <port>to confirm the kernel’s view of the destination. Containerised Grafana without sidecar DNS is the most common cause of “connection refused” on a*.svcURL.
Security implications
- The browser never holds the secret. Confirm by viewing
any panel in the browser’s developer tools; the outbound
network requests go to Grafana, not the backend. Setting
access: directbreaks this property and exposes the secret to anyone who can read the dashboard URL. secureJsonDatais encrypted at rest with the server’ssecret_key. The encryption is reversible by the server, so it is not a credential vault — it is a secret-on-disk barrier. Treat it as “the secret is on the server” and not as “the secret is safe”.- Read-only tokens. Every backend in this stack supports a
read-only credential. Issue one per Grafana; never reuse an
admin token. The Prometheus
prom-proddata source should hold a token with read permission only; alerting writes go through Prometheus’s own/api/v1/admin/*API with a separate credential. - mTLS / TLS termination. If the backend is on
https://...and the certificate is internal (acquired from a private CA),jsonData.tlsSkipVerify: trueis a temptation; the right control is to install the CA into the Grafana container’s trust store.
Performance implications
- A panel makes one request per data source per refresh. The
timeIntervalfield caps the rate at which Grafana may ask the backend. The default (1sfor Prometheus) suits most dashboards; tight intervals (200ms) amplify load and should be reserved for short auto-refresh bursts. - Loki queries are streamed; the
maxLinescap prevents a panel from requesting hundreds of thousands of lines at once. Set it to what a human can read (100 to 1000). - Tempo’s
tracesToLogsandserviceMapcause cross-data-source fetches. The first log search inside a trace view makes a Loki query; the first service-map render makes a Prometheus query. These are unbounded in production graphs; govern withmanagedAlert/managedDashboardpolicies, not by patching every panel. ${PROM_READ_TOKEN}reads from the environment on every poll. A slow secret-manager read on each poll is a known pattern; cache the value into the environment once on container start.
Production guidance
- Provision, do not UI-edit.
editable: falsebelongs on every production data source. - Pin a UID once. Add a CI check that fails when a UID changes.
- Secure-JSON fields reference a secrets manager via env vars.
Commit a
.env.example, not the secrets. - One read token per backend. Issue a new one when an operator leaves the team; rotate on a fixed cadence (90 days is common).
- Keep the plugin set small. Each plugin is an attack surface and a version-pinning liability.
- Treat “no data” on a panel as a data-source-health event first and a panel-config event second.
Verification
You should now be able to answer:
- What is the UID for, and why is it different from the name?
- What does
secureJsonDatabuy you thatjsonDatadoes not? - Why is
access: proxythe secure default for internet-exposed Grafana? - Which two endpoints together prove a panel can render?
- How does Grafana stop you from racing the provisioning file with a UI edit?
Quiz
Knowledge check · 8 questions
Q1. What identifies a data source in a dashboard query and survives data source renames?
Q2. Which field stores a backend password that must not appear in the API output?
Q3. Setting access: direct prevents the browser from ever holding a data source credential.
Q4. What is the first URL to inspect when a panel shows no data?
Q5. Name one pair of endpoints whose 200 codes together confirm a panel will render.
Q6. Which symptoms point to a wrong UID rather than a wrong URL?
Q7. What does setting editable: false on a provisioned data source prevent?
Q8. Where does Grafana store the data source credentials at rest?
Passing score: 75%. Answers are checked in this browser.