{"record":{"id":"d97227c0fb983fb4","repo":"fatedier/frp","slug":"xtcp-server-for-s-doesn-t-exist","errorCode":null,"errorMessage":"xtcp server for [%s] doesn't exist","messagePattern":"xtcp server for \\[(.+?)\\] doesn't exist","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/nathole/controller.go","lineNumber":187,"sourceCode":"\n\tsid := c.GenSid()\n\tsession := &Session{\n\t\tsid:                sid,\n\t\tvisitorMsg:         m,\n\t\tvisitorTransporter: transporter,\n\t\tnotifyCh:           make(chan struct{}, 1),\n\t}\n\tvar (\n\t\tclientCfg *ClientCfg\n\t\tok        bool\n\t)\n\terr := func() error {\n\t\tc.mu.Lock()\n\t\tdefer c.mu.Unlock()\n\n\t\tclientCfg, ok = c.clientCfgs[m.ProxyName]\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"xtcp server for [%s] doesn't exist\", m.ProxyName)\n\t\t}\n\t\tif !util.ConstantTimeEqString(m.SignKey, util.GetAuthKey(clientCfg.sk, m.Timestamp)) {\n\t\t\treturn fmt.Errorf(\"xtcp connection of [%s] auth failed\", m.ProxyName)\n\t\t}\n\t\tc.sessions[sid] = session\n\t\treturn nil\n\t}()\n\tif err != nil {\n\t\tlog.Warnf(\"handle visitorMsg error: %v\", err)\n\t\t_ = transporter.Send(c.GenNatHoleResponse(m.TransactionID, nil, err.Error()))\n\t\treturn\n\t}\n\tlog.Tracef(\"handle visitor message, sid [%s], server name: %s\", sid, m.ProxyName)\n\n\tdefer func() {\n\t\tc.mu.Lock()\n\t\tdefer c.mu.Unlock()\n\t\tdelete(c.sessions, sid)","sourceCodeStart":169,"sourceCodeEnd":205,"githubUrl":"https://github.com/fatedier/frp/blob/6c8a8d0a97d03b44e9528d30b30c70cb9d61b405/pkg/nathole/controller.go#L169-L205","documentation":"Thrown in the nathole Controller's visitor-message handler (pkg/nathole/controller.go) when a NatHoleVisitor message arrives for a proxy name that has no registered xtcp client configuration. The controller looks up c.clientCfgs[m.ProxyName] under lock; a miss means no client-side xtcp server is currently registered under that name, so hole punching cannot proceed and the error is sent back to the visitor inside a NatHoleResp.","triggerScenarios":"A visitor sends a NatHoleVisitor message for proxyName that was never registered via ListenClient, or whose registration was removed (client disconnected / CloseClient called) before the visitor's request arrived.","commonSituations":"Visitor config's serverName does not match the client's xtcp proxy name; the frpc hosting the xtcp proxy is offline or has not yet registered when the visitor connects; the client disconnected between registration and the visitor's punch attempt.","solutions":["Set the visitor's serverName in frpc.toml to exactly the client's xtcp proxy name","Verify the client owning the xtcp proxy is connected and its proxy registered successfully (check frps dashboard/logs)","Retry from the visitor once the client is confirmed online","Check for typos and whitespace differences in proxy names between the two configs"],"exampleFix":"# before (visitor config, wrong target)\n[[visitors]]\nname = \"p2p-v\"\ntype = \"xtcp\"\nserverName = \"p2p-others\"\n\n# after\n[[visitors]]\nname = \"p2p-v\"\ntype = \"xtcp\"\nserverName = \"p2p\"   # must equal the client's xtcp proxy name","handlingStrategy":"validation","validationCode":null,"typeGuard":null,"tryCatchPattern":"if err := nathole.PreCheck(ctx, tp, proxyName, timeout); err != nil {\n    if strings.Contains(err.Error(), \"doesn't exist\") {\n        // client not registered yet: verify serverName, wait for client, retry\n    }\n    return err\n}","preventionTips":["Visitor's serverName must exactly equal the client's xtcp proxy name","Run PreCheck before dialing to fail fast","Confirm the client's proxy is registered (frps logs/dashboard) before visiting"],"tags":["go","frp","nathole","xtcp","configuration","visitor"],"backgroundTag":null,"analyzedSha":"6c8a8d0a97d03b44e9528d30b30c70cb9d61b405","analyzedAt":"2026-08-15T06:53:27.215Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}