{"record":{"id":"418ec84437b3e581","repo":"tailscale/tailscale","slug":"peer-v-not-authorized-not-found-in-local-tailsca","errorCode":null,"errorMessage":"peer %v not authorized (not found in local tailscaled)","messagePattern":"peer (.+?) not authorized \\(not found in local tailscaled\\)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"derp/derpserver/derpserver.go","lineNumber":1612,"sourceCode":"// verifyClient checks whether the client is allowed to connect to the derper,\n// depending on how & whether the server's been configured to verify.\nfunc (s *Server) verifyClient(ctx context.Context, clientKey key.NodePublic, info *derp.ClientInfo, clientIP netip.Addr) error {\n\tif s.isMeshPeer(info) {\n\t\t// Trusted mesh peer. No need to verify further. In fact, verifying\n\t\t// further wouldn't work: it's not part of the tailnet so tailscaled and\n\t\t// likely the admission control URL wouldn't know about it.\n\t\treturn nil\n\t}\n\n\tif info != nil && s.disallowedAppNames.Contains(info.AppName) {\n\t\treturn fmt.Errorf(\"disallowed app name %q\", info.AppName)\n\t}\n\n\t// tailscaled-based verification:\n\tif s.verifyClientsLocalTailscaled {\n\t\t_, err := s.localClient.WhoIsNodeKey(ctx, clientKey)\n\t\tif err == local.ErrPeerNotFound {\n\t\t\treturn fmt.Errorf(\"peer %v not authorized (not found in local tailscaled)\", clientKey)\n\t\t}\n\t\tif err != nil {\n\t\t\tif strings.Contains(err.Error(), \"invalid 'addr' parameter\") {\n\t\t\t\t// Issue 12617\n\t\t\t\treturn errors.New(\"tailscaled version is too old (out of sync with derper binary)\")\n\t\t\t}\n\t\t\treturn fmt.Errorf(\"failed to query local tailscaled status for %v: %w\", clientKey, err)\n\t\t}\n\t}\n\n\t// admission controller-based verification:\n\tif s.verifyClientsURL != \"\" {\n\t\tctx, cancel := context.WithTimeout(ctx, 5*time.Second)\n\t\tdefer cancel()\n\n\t\tjreq, err := json.Marshal(&tailcfg.DERPAdmitClientRequest{\n\t\t\tNodePublic: clientKey,\n\t\t\tSource:     clientIP,","sourceCodeStart":1594,"sourceCodeEnd":1630,"githubUrl":"https://github.com/tailscale/tailscale/blob/a7769cbc33a3eba62bb16fc803b97077c2969d16/derp/derpserver/derpserver.go#L1594-L1630","documentation":"With -verify-clients (verifyClientsLocalTailscaled), Server.verifyClient (derpserver.go:1593) asks the local tailscaled's WhoIsNodeKey about the connecting node; local.ErrPeerNotFound maps to this explicit refusal. It means the node key is not present in the tailnet that the derper host's tailscaled belongs to, so the client is rejected before relaying anything. Note the mesh-peer fast path above it: peers with the mesh token skip this check entirely.","triggerScenarios":"A node from a different tailnet connects; the derper host's tailscaled is logged into another tailnet than the connecting nodes; the node was removed or its key expired; headscale serving multiple tailnets through one derper.","commonSituations":"Shared derper fronting multiple tailnets without an admission controller; derper container whose tailscaled sidecar joined the wrong tailnet; stale node keys after a tailnet reset retrying the old DERP map.","solutions":["Log the derper host's tailscaled into the same tailnet as the nodes connecting to it","Re-add or re-authenticate the rejected node in the control plane and retry","If one derper must serve multiple tailnets, use -verify-clients-url admission control that decides per node key","Otherwise point the node at a DERP server whose verification path accepts it, or stop running with -verify-clients"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Pre-flight: would the local tailscaled know this node?\nif _, err := localClient.WhoIsNodeKey(ctx, nodeKey); err != nil {\n\tif errors.Is(err, local.ErrPeerNotFound) {\n\t\treturn fmt.Errorf(\"node %v is not in this tailnet; fix tailnet membership before pointing it here\", nodeKey)\n\t}\n\treturn fmt.Errorf(\"tailscaled check failed: %w\", err)\n}","typeGuard":"// Distinguish 'unknown peer' from other local-API failures\nfunc isPeerNotFound(err error) bool {\n\treturn errors.Is(err, local.ErrPeerNotFound)\n}","tryCatchPattern":"In Go: _, err := s.localClient.WhoIsNodeKey(ctx, clientKey); switch { case errors.Is(err, local.ErrPeerNotFound): reject as unauthorized; case err != nil: surface as infrastructure failure with retry; default: admit } — the code already splits the first case; keep the distinction visible to operators.","preventionTips":["Keep the derper host's tailscaled in the same tailnet as its clients","Use --verify-clients-url when one derper serves multiple tailnets","Re-check node membership after tailnet resets or user removals"],"tags":["go","tailscale","derp-server","authorization","verification","tailscaled"],"backgroundTag":"client-verification-failed","analyzedSha":"a7769cbc33a3eba62bb16fc803b97077c2969d16","analyzedAt":"2026-08-18T08:17:25.280Z","contentChangedAt":"2026-08-18T08:17:25.280Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}