istio/istio · error
invalid prometheus scrape configuration: target port %s is t
Error message
invalid prometheus scrape configuration: target port %s is the same as agent port, which may lead to a recursive loop
What it means
New-format validation in NewServer: a targets[] entry's numeric port equals the agent status port (config.StatusPort, default 15020). The agent would scrape its own merged /metrics endpoint — which already contains that data — producing an infinite recursive scrape loop, so startup is rejected. This is the multi-target analogue of the legacy-path check, comparing values numerically so "015020" is also caught.
Source
Thrown at pilot/cmd/pilot-agent/status/server.go:316
s.prometheus.Port = s.prometheus.Targets[0].Port
}
if s.prometheus.Path == "" {
s.prometheus.Path = s.prometheus.Targets[0].Path
}
}
// Default path and validate every target port numerically to catch leading-zero
// representations (e.g. "015020" dials 15020 at runtime but != "15020" as a string).
for i, t := range s.prometheus.Targets {
if t.Path == "" {
s.prometheus.Targets[i].Path = "/metrics"
}
portNum, atoiErr := strconv.Atoi(t.Port)
if atoiErr != nil || portNum < 1 || portNum > 65535 {
return nil, fmt.Errorf("invalid prometheus scrape configuration: "+
"invalid target port %q", t.Port)
}
if portNum == int(config.StatusPort) {
return nil, fmt.Errorf("invalid prometheus scrape configuration: "+
"target port %s is the same as agent port, which may lead to a recursive loop", t.Port)
}
if reason, reserved := IstioReservedPortReason(strconv.Itoa(portNum)); reserved {
return nil, fmt.Errorf("invalid prometheus scrape configuration: "+
"target port %s is reserved for Istio (%s) and cannot be scraped", t.Port, reason)
}
}
}
}
if config.KubeAppProbers == "" {
return s, nil
}
if err := json.Unmarshal([]byte(config.KubeAppProbers), &s.appKubeProbers); err != nil {
return nil, fmt.Errorf("failed to decode app prober err = %v, json string = %v", err, config.KubeAppProbers)
}
s.appProbeClient = make(map[string]*http.Client, len(s.appKubeProbers))View on GitHub (pinned to 8dc789c5cf)
Solutions
- Remove or re-point the offending target (the error names the port) to the application's real metrics port
- Never include 15020 (or your customized status port) in targets — Envoy metrics are already merged automatically
- Normalize ports in your config pipeline to canonical decimal form (strip leading zeros) so string and numeric views agree
- Validate targets with IstioReservedPortReason before applying; it also flags this class of mistake
Example fix
// before
{"scrape":"true","targets":[{"port":"8080"},{"port":"15020"}]}
// after
{"scrape":"true","targets":[{"port":"8080"},{"port":"9090","path":"/actuator/prometheus"}]} Defensive patterns
Strategy: validation
Validate before calling
statusPort := 15020 // config.StatusPort used by the agent
for _, t := range cfg.Targets {
if n, _ := strconv.Atoi(t.Port); n == statusPort {
return fmt.Errorf("target %s scrapes the agent itself (recursion)", t.Port)
}
} Prevention
- Envoy metrics are auto-merged — never add 15020 or the status port as a target
- Normalize away leading zeros when templating ports ("015020" == 15020 numerically)
When it happens
Trigger: PrometheusScrapingConfig with scrape != false and a targets array where some target's port Atoi-parses to exactly config.StatusPort — e.g. {"port":"15020"} or {"port":"015020"}.
Common situations: Copying the merged port 15020 into the new multi-target list out of habit from the prometheus.io/port annotation; statusPort customized via flags to a value someone also used for an app metrics port; leading-zero ports sneaking through templating.
Related errors
- invalid prometheus scrape configuration: invalid target port
- invalid prometheus scrape configuration: target port %s is r
- failed to unmarshal %s: %v
- invalid prometheus scrape configuration: application port is
- empty port in scrape target %q
AI-assisted analysis of istio/istio@8dc789c5cf (2026-08-15).
Data as JSON: /api/errors/cdaf5f08c4c409b7.
Report an issue: GitHub.