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

  1. Remove or re-point the offending target (the error names the port) to the application's real metrics port
  2. Never include 15020 (or your customized status port) in targets — Envoy metrics are already merged automatically
  3. Normalize ports in your config pipeline to canonical decimal form (strip leading zeros) so string and numeric views agree
  4. 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

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


AI-assisted analysis of istio/istio@8dc789c5cf (2026-08-15). Data as JSON: /api/errors/cdaf5f08c4c409b7. Report an issue: GitHub.