OpenNHP/opennhp · error

loopback peer sent no X-Real-IP header; check reverse proxy…

Error message

loopback peer %s sent no X-Real-IP header; check reverse proxy config

What it means

Raised by realClientAddr when an HTTP request arrives from a loopback peer (the reverse proxy on the same host) but carries no X-Real-IP header. Without it the client address would default to the loopback address, which the server's isRoutablePublicIP check rejects downstream, producing hard-to-diagnose silent 504s — hence this loud, early error pointing at the reverse proxy configuration.

Solutions

  1. Configure the reverse proxy (nginx/caddy) to set X-Real-IP $remote_addr on proxied requests
  2. Make sure the proxy header is passed to the relay's HTTP handler and not stripped
  3. Restart/reload the proxy after the change
Defensive patterns

Strategy: validation

When it happens

Trigger: Thrown at endpoints/relay/relay.go:1168 when the library encounters an invalid state.

Common situations: See trigger scenarios.


AI-assisted analysis of OpenNHP/opennhp@6e04ca5ff0 (2026-09-07). Data as JSON: /api/errors/082db7bbd639d7a5. Report an issue: GitHub.

Appendix: source

Thrown at endpoints/relay/relay.go:1168

// which the server's isRoutablePublicIP check rejects, producing
// silent 504s that are hard to diagnose. A loud error here points
// operators at the misconfigured reverse proxy instead.
func realClientAddr(r *http.Request) (*net.UDPAddr, error) {
	// Parse the direct TCP peer first so we always have a port.
	peerHost, peerPortStr, err := net.SplitHostPort(r.RemoteAddr)
	peerIP := net.IPv4zero
	peerPort := 0
	if err == nil {
		if ip := net.ParseIP(peerHost); ip != nil {
			peerIP = ip
		}
		_, _ = fmt.Sscanf(peerPortStr, "%d", &peerPort)
	}

	if peerIP.IsLoopback() {
		realIP := strings.TrimSpace(r.Header.Get("X-Real-IP"))
		if realIP == "" {
			return nil, fmt.Errorf("loopback peer %s sent no X-Real-IP header; check reverse proxy config", r.RemoteAddr)
		}
		ip := net.ParseIP(realIP)
		if ip == nil {
			return nil, fmt.Errorf("loopback peer %s sent malformed X-Real-IP %q", r.RemoteAddr, realIP)
		}
		// X-Real-IP carries no port; the proxy peer's port is
		// used so connection-tracking keys remain unique.
		return &net.UDPAddr{IP: ip, Port: peerPort}, nil
	}

	return &net.UDPAddr{IP: peerIP, Port: peerPort}, nil
}

View on GitHub (pinned to 6e04ca5ff0)