{"record":{"id":"5dc7d452ba9ded3b","repo":"OpenNHP/opennhp","slug":"relay-misconfigured-missing-x-real-ip-header-from","errorCode":null,"errorMessage":"relay misconfigured: missing X-Real-IP header from local reverse proxy","messagePattern":"relay misconfigured: missing X-Real-IP header from local reverse proxy","errorType":"http","errorClass":null,"httpStatus":502,"severity":"error","filePath":"endpoints/relay/relay.go","lineNumber":988,"sourceCode":"\t\treturn\n\t}\n\tn := len(innerPacket)\n\n\t// Extract the counter from the inner packet header (bytes [16:24], big-endian uint64).\n\t// The NHP server echoes this counter in its ACK/COK response, so we use it\n\t// to match the response back to this HTTP request.\n\tif n < 24 {\n\t\thttp.Error(w, \"inner packet too short\", http.StatusBadRequest)\n\t\treturn\n\t}\n\tinnerCounter := binary.BigEndian.Uint64(innerPacket[16:24])\n\n\t// Extract real client address before picking an instance so sticky\n\t// sessions can hash on it.\n\trealAddr, err := realClientAddr(r)\n\tif err != nil {\n\t\tlog.Error(\"[Relay] %v\", err)\n\t\thttp.Error(w, \"relay misconfigured: missing X-Real-IP header from local reverse proxy\", http.StatusBadGateway)\n\t\treturn\n\t}\n\trealAddrKey := realAddr.String()\n\n\t// Pick a target instance. When StickyInstance is enabled,\n\t// hash the real client IP so the same client always reaches the same\n\t// instance — required for stateful flows like OTP→REG where per-\n\t// instance local state (SQLite) must be consistent across requests.\n\t// When disabled (default), each request is load-balanced independently.\n\tvar inst *serverInstance\n\tif cr.sticky && len(cr.instances) > 1 {\n\t\tvar ok bool\n\t\tinst, ok = cr.picker.PickByKey(realAddrKey)\n\t\tif !ok {\n\t\t\thttp.Error(w, \"server has no usable instance\", http.StatusServiceUnavailable)\n\t\t\treturn\n\t\t}\n\t} else {","sourceCodeStart":970,"sourceCodeEnd":1006,"githubUrl":"https://github.com/OpenNHP/opennhp/blob/6e04ca5ff03222a699c24205cd4bf8fee9af7ffe/endpoints/relay/relay.go#L970-L1006","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"# before: nginx location missing header\nlocation /relay {\n    proxy_pass http://127.0.0.1:PORT;\n}\n# after\nlocation /relay {\n    proxy_set_header X-Real-IP $remote_addr;\n    proxy_pass http://127.0.0.1:PORT;\n}","handlingStrategy":"fallback","validationCode":"// ensure the reverse proxy sets the header before traffic flows:\n// nginx: proxy_set_header X-Real-IP $remote_addr;\n// smoke test through the proxy and inspect relay response for 502","typeGuard":null,"tryCatchPattern":"if resp.StatusCode == http.StatusBadGateway {\n    b, _ := io.ReadAll(resp.Body)\n    if strings.Contains(string(b), \"X-Real-IP\") {\n        return errors.New(\"relay not reached through reverse proxy; fix proxy X-Real-IP config\")\n    }\n}","preventionTips":["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"],"tags":["http","configuration","proxy"],"backgroundTag":"missing-required-config-field","analyzedSha":"6e04ca5ff03222a699c24205cd4bf8fee9af7ffe","analyzedAt":"2026-09-07T15:44:59.941Z","contentChangedAt":"2026-09-07T15:44:59.941Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}