| title | Prometheus Context Deadline Exceeded | |||||
|---|---|---|---|---|---|---|
| slug | prometheus-context-deadline-exceeded | |||||
| technologies |
|
|||||
| severity | high | |||||
| tags |
|
|||||
| related |
|
|||||
| last_reviewed | 2026-06-27 |
Get "http://10.0.4.21:9100/metrics": context deadline exceeded
A common variant when TLS or the headers stall:
Get "https://app:8443/metrics": context deadline exceeded (Client.Timeout exceeded while awaiting headers)
Prometheus bounds every scrape with a scrape_timeout (default 10s, never
larger than scrape_interval). When the target does not return a full,
parseable response within that window, Go cancels the request context and the
scrape fails with context deadline exceeded. The target may be reachable and
even partway through responding β the problem is latency, not refusal. The
target is marked DOWN and up goes to 0, exactly like a refused connection,
but the cause and fix are completely different.
- prometheus (scrape manager)
high β a timing-out target reports no metrics. Worse, a slow /metrics
endpoint often signals the target itself is under load, so you lose visibility
precisely when you most need it.
- The exporter renders a very large or high-cardinality
/metricspayload that takes longer thanscrape_timeoutto generate or transfer. - The target host is CPU-starved or swapping, so the handler is slow.
scrape_timeoutis set too low for a legitimately heavy endpoint (e.g.kube-state-metricson a large cluster).- A slow TLS handshake or a proxy in the path adds latency.
The scrape is wrapped in a context.WithTimeout(scrape_timeout). The clock
starts at the moment Prometheus initiates the request and covers DNS, connect,
TLS, and reading the entire body. If the exporter computes metrics lazily on
each scrape (many do), a growing series count linearly increases response time
until it crosses the deadline. Because the failure is time-based, it often
appears intermittently first β some scrapes squeak in under the limit β before
becoming permanent.
# Which targets are timing out, per the API
curl -s http://localhost:9090/api/v1/targets \
| jq '.data.activeTargets[] | select(.lastError|test("deadline")) | {url:.scrapeUrl, lastScrapeDuration, lastError}'
# Measure how long the target actually takes to respond, with byte size
curl -s -o /dev/null -w 'time=%{time_total}s size=%{size_download}bytes\n' http://10.0.4.21:9100/metrics
# How big is the response β count exposed series
curl -s http://10.0.4.21:9100/metrics | grep -vc '^#'time=12.84s size=41203338bytes
A response that takes longer than scrape_timeout (e.g. 12.8s against a 10s
timeout) confirms the diagnosis. lastScrapeDuration in the API will hover at
or above the timeout. A healthy target returns in well under a second.
-
Reduce the payload at the source: drop unneeded high-cardinality metrics with
metric_relabel_configsdropactions, or disable expensive collectors. -
If the endpoint is legitimately heavy, raise the timeout (and interval if needed) for that job only:
- job_name: kube-state-metrics scrape_interval: 60s scrape_timeout: 55s # must be <= scrape_interval
-
Fix host resource pressure (CPU/memory) on the target so the handler is fast.
-
Reload Prometheus and watch
scrape_duration_secondsfor the job.
# Scrape duration should sit comfortably under the timeout
curl -s 'http://localhost:9090/api/v1/query?query=scrape_duration_seconds{instance="10.0.4.21:9100"}' \
| jq '.data.result[].value[1]'
# Expect a value well below scrape_timeout, and up == 1- Alert on
scrape_duration_seconds / scrape_timeout > 0.8to catch slow scrapes before they fail. - Keep
/metricssmall: trim labels and cardinality at the exporter. - Never set
scrape_timeoutlarger thanscrape_intervalβ Prometheus rejects the config.
prometheus Β· scraping Β· timeout Β· performance Β· production