Prometheus Error Guide: 'missing labels' — Restore Labels That Break Joins & Routing
Fix missing Prometheus labels that break PromQL joins, alert routing, and dashboards: relabel_configs vs metric_relabel_configs, honor_labels, and __meta_* target labels.
- #prometheus
- #monitoring
- #troubleshooting
- #errors
Stuck on this Prometheus & Monitoring error? Get the free incident triage checklist
A one-page PDF — the exact steps to isolate, fix, and verify a production error like this one. No spam, unsubscribe anytime.
Overview
There is no crash for a missing label — the symptom is a series that renders but lacks a label you rely on, so joins return nothing and routing silently misfires. In the expression browser the series is missing the team (or env, cluster) label you expected:
# expected
http_requests_total{job="api", instance="node-1:9100", team="payments"}
# actual — team is gone
http_requests_total{job="api", instance="node-1:9100"}
A PromQL join built on that label then evaluates to empty:
Empty query result
Symptoms
- A dashboard panel or alert that filters/groups by a label suddenly shows no data.
- A vector match (
on(...)/group_left) returns an empty result even though both metrics exist. - Alertmanager routes an alert to the wrong receiver because a routing label (
team,severity,env) is absent. - The series exists in the expression browser but is missing a label present on other targets.
- A relabeling change deployed recently and labels disappeared right after.
Common Root Causes
- A
relabel_configsrule dropped or overwrote the label before the scrape (target labels). - A
metric_relabel_configsrule stripped the label after the scrape but before ingestion. - The exporter never exposed the label, and you assumed service discovery would add it.
__meta_*service-discovery labels were not mapped into real labels, so they were discarded.honor_labelshandling — an exporter (like Pushgateway) set a label the server overwrote, or vice versa.- A join using
on()with the wrong label set, so matching labels do not line up between the two sides. - Inconsistent labels across targets — some instances carry the label, some do not.
Diagnostic Workflow
Start in the expression browser. Query the bare metric and read the actual label set on the returned series:
http_requests_total{job="api"}
If the label is missing, check whether it is being produced at scrape time. up carries the target labels, so it is a quick way to see what the server attached:
up{job="api"}
Look at the scrape config. relabel_configs acts on target/__meta_* labels before the scrape; metric_relabel_configs acts on every sample after the scrape:
# prometheus.yml
scrape_configs:
- job_name: api
kubernetes_sd_configs:
- role: pod
relabel_configs:
# keep only pods we mean to scrape
- source_labels: [__meta_kubernetes_pod_label_app]
regex: api
action: keep
# map a discovery meta-label into a real label
- source_labels: [__meta_kubernetes_pod_label_team]
target_label: team
- source_labels: [__meta_kubernetes_pod_node_name]
target_label: node
metric_relabel_configs:
# this kind of rule silently deletes a label
- regex: team
action: labeldrop
The labeldrop above is a classic culprit — it removes team from every sample after scrape. Removing it restores the label. To confirm which meta-labels are available for mapping, open Status > Targets in the UI and expand the target’s “Discovered Labels”.
For a join that returns empty, verify both sides share the matching labels. A one-to-many join needs group_left and a common label set:
sum by (instance) (rate(http_requests_total{job="api"}[5m]))
* on(instance) group_left(team)
node_meta{job="api"}
If node_meta lacks instance in the same format (e.g. node-1:9100 vs node-1), the on(instance) match fails and you get an empty result. Normalize the label with relabeling so both sides agree.
When ingesting from Pushgateway or a federation endpoint, honor_labels decides who wins on a conflict:
- job_name: pushgateway
honor_labels: true # keep labels the source sent, don't overwrite with job/instance
static_configs:
- targets: ['node-1:9091']
Validate the whole config before reloading:
promtool check config prometheus.yml
curl -s http://node-1:9090/api/v1/targets | jq '.data.activeTargets[] | {job: .labels.job, discovered: .discoveredLabels}'
Example Root Cause Analysis
A cost-attribution dashboard grouped http_requests_total by team, and one service’s panels went blank. Querying http_requests_total{job="api"} in the expression browser showed the series present but with no team label, while other jobs still had it. up{job="api"} confirmed the target was healthy, so this was a labeling problem, not a scrape failure.
Reading the scrape config revealed a recently added metric_relabel_configs rule with action: labeldrop and regex: team — added to reduce cardinality on a different, high-churn job but applied to the wrong job_name. Because metric_relabel_configs runs after the scrape, the label was collected and then thrown away. Removing that rule from the api job restored team on every sample, and the dashboard’s sum by (team) grouping came back on the next scrape.
Prevention Best Practices
- Know the boundary:
relabel_configsshapes target labels before scrape;metric_relabel_configsshapes samples after scrape. Use the right one. - Map
__meta_*discovery labels into real labels explicitly — they are dropped unless youtarget_labelthem. - Keep label names and formats consistent across targets so
on()joins line up (especiallyinstance). - Set
honor_labelsdeliberately for Pushgateway and federation, and document why. - Run
promtool check configin CI and diffdiscoveredLabelsfrom the targets API after relabeling changes. - Prefer
group_left/group_rightwith an explicit label list so joins are readable and intentional.
Quick Command Reference
# Validate config and view target labels
promtool check config prometheus.yml
curl -s http://node-1:9090/api/v1/targets \
| jq '.data.activeTargets[] | {job: .labels.job, labels: .labels}'
# Inspect the real label set on a series
http_requests_total{job="api"}
# See what target labels the server attached
up{job="api"}
# A join that needs a shared, correctly-formatted match label
rate(http_requests_total{job="api"}[5m]) * on(instance) group_left(team) node_meta
Conclusion
Missing labels are almost always self-inflicted by relabeling: a labeldrop, an overwrite, or an unmapped __meta_* label. Confirm the actual label set in the expression browser, decide whether the problem is before scrape (relabel_configs) or after (metric_relabel_configs), and keep join labels consistent across targets. Validate every change with promtool and the targets API. More configuration walkthroughs are in the Prometheus stack guide.
Fixed it? Get 500 Prometheus & Monitoring & DevOps AI prompts — free
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.
Did this fix your issue?
Get 500 Battle-Tested DevOps AI Prompts — Free
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.