{"record":{"id":"12732568d0da154f","repo":"tailscale/tailscale","slug":"requested-certificate-for-ip-v-does-not-match-the","errorCode":null,"errorMessage":"requested certificate for IP %v does not match the connection's IP address","messagePattern":"requested certificate for IP (.+?) does not match the connection's IP address","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cmd/derper/ipcert.go","lineNumber":186,"sourceCode":"\t\treturn netip.Addr{}, false\n\t}\n\tip := ta.AddrPort().Addr().Unmap()\n\treturn ip, ip.IsValid()\n}\n\nfunc (m *ipCertManager) getCertificate(hi *tls.ClientHelloInfo) (*tls.Certificate, error) {\n\tconnIP, connIPOK := connLocalIP(hi)\n\tif hi.ServerName != \"\" {\n\t\tsniIP, err := netip.ParseAddr(hi.ServerName)\n\t\tif err != nil {\n\t\t\t// The SNI is a DNS name; let the hostname provider handle it.\n\t\t\tif m.nextTLS != nil && m.nextTLS.GetCertificate != nil {\n\t\t\t\treturn m.nextTLS.GetCertificate(hi)\n\t\t\t}\n\t\t\treturn nil, fmt.Errorf(\"no certificate for hostname %q; this server only serves IP address certificates\", hi.ServerName)\n\t\t}\n\t\tif !connIPOK || sniIP.Unmap() != connIP {\n\t\t\treturn nil, fmt.Errorf(\"requested certificate for IP %v does not match the connection's IP address\", sniIP)\n\t\t}\n\t}\n\tif !connIPOK {\n\t\treturn nil, errors.New(\"unable to determine the connection's local IP address\")\n\t}\n\tctx := hi.Context()\n\tif ctx == nil {\n\t\tctx = context.Background()\n\t}\n\treturn m.certForIP(ctx, connIP)\n}\n\n// certForIP returns the current certificate for ip, obtaining one\n// first if there is no unexpired certificate for it. Concurrent\n// callers for the same IP share a single issuance.\nfunc (m *ipCertManager) certForIP(ctx context.Context, ip netip.Addr) (*tls.Certificate, error) {\n\tm.mu.Lock()\n\te := m.entryLocked(ip)","sourceCodeStart":168,"sourceCodeEnd":204,"githubUrl":"https://github.com/tailscale/tailscale/blob/cfe32b8be6a33f8e24fbc369cbfbf7c729d9e042/cmd/derper/ipcert.go#L168-L204","documentation":"A defensive validation in ipCertManager.getCertificate: the SNI parsed as an IP literal, but after Unmap it does not equal the connection's local IP address (or the local IP could not be determined). The server refuses to serve a certificate for an IP it is not actually terminating the connection on, preventing issuance loops and mis-issuance attempts via spoofed SNI.","triggerScenarios":"Client dials IP-B but sends SNI for IP-A (different address); more commonly, network address translation rewrites the destination: derper behind Docker port-publish, k8s NodePort/LoadBalancer, or a cloud LB doing DNAT, so hi.Conn.LocalAddr is a private IP (10.x/172.x) while the SNI carries the public IP. !connIPOK (non-IP local addr) also triggers it when SNI is an IP.","commonSituations":"Containerized derper where the cert is for the public IP but the listener sees the pod IP; multi-homed hosts where the TLS listener binds the wrong interface; clients behind 1:1 NAT; health probes that set an arbitrary SNI IP.","solutions":["Run derper directly on the host that owns the public IP (host network mode in Docker: --network=host) so LocalAddr matches the certified IP.","If NAT is unavoidable, switch to DNS-name certificate mode for that derper and use hostname SNI instead of IP SNI.","Verify what the server sees: log hi.Conn.LocalAddr() and compare with the SNI value to identify the rewriting layer."],"exampleFix":"# before: container NAT breaks SNI-vs-local-IP equality\ndocker run -p 443:443 -p 80:80 derper ...   # LocalAddr is the container IP\n\n# after: host networking so the cert IP equals the connection IP\ndocker run --network=host derper ...","handlingStrategy":"validation","validationCode":"// Before requesting an IP cert, assert SNI equals the connection's local IP.\nfunc sniMatchesConn(hi *tls.ClientHelloInfo) bool {\n    sni, err := netip.ParseAddr(hi.ServerName)\n    if err != nil { return false }\n    local, ok := connLocalIP(hi)\n    return ok && sni.Unmap() == local\n}","typeGuard":null,"tryCatchPattern":"if _, err := mgr.GetCertificate(hi); err != nil {\n    if strings.Contains(err.Error(), \"does not match the connection's IP\") {\n        // deployment problem: NAT/container rewriting dst; fix network, don't retry\n    }\n}","preventionTips":["Run derper with host networking or directly on the public-IP interface.","Avoid DNAT/port-publish front-ends in front of an IP-cert derper.","Log hi.Conn.LocalAddr() during bring-up and compare with the certified IP.","Prefer DNS-name certs when the server sits behind NAT."],"tags":["go","tls","nat","derper","networking","docker"],"backgroundTag":null,"analyzedSha":"cfe32b8be6a33f8e24fbc369cbfbf7c729d9e042","analyzedAt":"2026-08-15T19:58:31.583Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}