OpenNHP/opennhp · error
relay misconfigured: missing X-Real-IP header from local…
Error message
relay misconfigured: missing X-Real-IP header from local reverse proxy
What it means
handleRelay responds with HTTP 502 'relay misconfigured: missing X-Real-IP header from local reverse proxy' when realClientAddr(r) fails because the request carries no X-Real-IP header. The relay is designed to sit behind a local reverse proxy that sets this header, and the real client address is required for sticky-session hashing; without it the request cannot be routed correctly.
Solutions
- Configure the reverse proxy to set X-Real-IP: nginx: proxy_set_header X-Real-IP $remote_addr;
- Route client traffic through the reverse proxy instead of hitting the relay port directly
- Check the proxy config for header-stripping (e.g. proxy_set_header X-Real-IP "") and remove it
- If a CDN/LB is in front, ensure it passes the client IP in X-Forwarded-For and the proxy derives X-Real-IP from it
Example fix
# before: nginx location missing header
location /relay {
proxy_pass http://127.0.0.1:PORT;
}
# after
location /relay {
proxy_set_header X-Real-IP $remote_addr;
proxy_pass http://127.0.0.1:PORT;
} Defensive patterns
Strategy: fallback
Validate before calling
// ensure the reverse proxy sets the header before traffic flows: // nginx: proxy_set_header X-Real-IP $remote_addr; // smoke test through the proxy and inspect relay response for 502
Try / catch
if resp.StatusCode == http.StatusBadGateway {
b, _ := io.ReadAll(resp.Body)
if strings.Contains(string(b), "X-Real-IP") {
return errors.New("relay not reached through reverse proxy; fix proxy X-Real-IP config")
}
} Prevention
- Always reach the relay through its reverse proxy, never the raw port
- Add proxy_set_header X-Real-IP $remote_addr to the proxy config
- Include X-Real-IP in deployment smoke tests
- Check CDN/LB forwards the client IP downstream
When it happens
Trigger: Sending requests directly to the relay port, bypassing the reverse proxy; a proxy (nginx, Caddy, traefik) that does not set X-Real-IP / X-Forwarded-For; proxy config that strips or fails to forward the header.
Common situations: Local development hitting the relay directly (curl localhost) instead of through nginx; newly deployed proxy without proxy_set_header X-Real-IP $remote_addr; load balancer terminating TLS and not passing client IP headers.
Understand the failure class
Background: "is required", "must be set", "missing required field": configuration validation errors across open-source libraries — this error's family across 36 libraries.
Related errors
- failed to download ztdo
- could not create request
- could not send https request
- unexpected status code
- could not read response body
AI-assisted analysis of OpenNHP/opennhp@6e04ca5ff0 (2026-09-07).
Data as JSON: /api/errors/5dc7d452ba9ded3b.
Report an issue: GitHub.
Appendix: source
Thrown at endpoints/relay/relay.go:988
return
}
n := len(innerPacket)
// Extract the counter from the inner packet header (bytes [16:24], big-endian uint64).
// The NHP server echoes this counter in its ACK/COK response, so we use it
// to match the response back to this HTTP request.
if n < 24 {
http.Error(w, "inner packet too short", http.StatusBadRequest)
return
}
innerCounter := binary.BigEndian.Uint64(innerPacket[16:24])
// Extract real client address before picking an instance so sticky
// sessions can hash on it.
realAddr, err := realClientAddr(r)
if err != nil {
log.Error("[Relay] %v", err)
http.Error(w, "relay misconfigured: missing X-Real-IP header from local reverse proxy", http.StatusBadGateway)
return
}
realAddrKey := realAddr.String()
// Pick a target instance. When StickyInstance is enabled,
// hash the real client IP so the same client always reaches the same
// instance — required for stateful flows like OTP→REG where per-
// instance local state (SQLite) must be consistent across requests.
// When disabled (default), each request is load-balanced independently.
var inst *serverInstance
if cr.sticky && len(cr.instances) > 1 {
var ok bool
inst, ok = cr.picker.PickByKey(realAddrKey)
if !ok {
http.Error(w, "server has no usable instance", http.StatusServiceUnavailable)
return
}
} else {View on GitHub (pinned to 6e04ca5ff0)