{"record":{"id":"19930d4edb0615c5","repo":"fatedier/frp","slug":"proxy-s-role-should-be-server-or-visitor","errorCode":null,"errorMessage":"proxy %s role should be 'server' or 'visitor'","messagePattern":"proxy (.+?) role should be 'server' or 'visitor'","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/config/legacy/client.go","lineNumber":283,"sourceCode":"\t\tif roleType == \"\" {\n\t\t\troleType = \"server\"\n\t\t}\n\n\t\tswitch roleType {\n\t\tcase \"server\":\n\t\t\tnewConf, newErr := NewProxyConfFromIni(prefix, name, section)\n\t\t\tif newErr != nil {\n\t\t\t\treturn nil, nil, fmt.Errorf(\"failed to parse proxy %s, err: %v\", name, newErr)\n\t\t\t}\n\t\t\tproxyConfs[prefix+name] = newConf\n\t\tcase \"visitor\":\n\t\t\tnewConf, newErr := NewVisitorConfFromIni(prefix, name, section)\n\t\t\tif newErr != nil {\n\t\t\t\treturn nil, nil, fmt.Errorf(\"failed to parse visitor %s, err: %v\", name, newErr)\n\t\t\t}\n\t\t\tvisitorConfs[prefix+name] = newConf\n\t\tdefault:\n\t\t\treturn nil, nil, fmt.Errorf(\"proxy %s role should be 'server' or 'visitor'\", name)\n\t\t}\n\t}\n\treturn proxyConfs, visitorConfs, nil\n}\n\nfunc renderRangeProxyTemplates(f *ini.File, section *ini.Section) error {\n\t// Validation\n\tlocalPortStr := section.Key(\"local_port\").String()\n\tremotePortStr := section.Key(\"remote_port\").String()\n\tif localPortStr == \"\" || remotePortStr == \"\" {\n\t\treturn fmt.Errorf(\"local_port or remote_port is empty\")\n\t}\n\n\tlocalPorts, err := util.ParseRangeNumbers(localPortStr)\n\tif err != nil {\n\t\treturn err\n\t}\n","sourceCodeStart":265,"sourceCodeEnd":301,"githubUrl":"https://github.com/fatedier/frp/blob/6c8a8d0a97d03b44e9528d30b30c70cb9d61b405/pkg/config/legacy/client.go#L265-L301","documentation":"Legacy ini loader: each non-common, non-range section must declare role = server or role = visitor (defaulting to server when role is empty). Any other value — including typos — hits the default branch and abort config loading with this error.","triggerScenarios":"A section with role = 'Server' (capitalized), 'srv', 'visit0r', or any string other than exactly 'server' or 'visitor'. Note the value is not trimmed beyond ini parsing, so trailing spaces also fail.","commonSituations":"Users upgrading from configs where role did not exist and guessing the value; case or spelling mistakes; copy-paste from docs of a different frp version that used different role names.","solutions":["Set role = server for exposing proxies or role = visitor for visitor sections, exact lowercase","Remove the role key entirely for plain proxies (server is the default)","Scan the config for stray whitespace or quotes around the role value"],"exampleFix":"; before\n[ssh]\nrole = Server\n\n; after (or just drop the key)\n[ssh]\nrole = server","handlingStrategy":"validation","validationCode":"role := section.Key(\"role\").String()\nif role != \"\" && role != \"server\" && role != \"visitor\" {\n    return fmt.Errorf(\"section %s: role must be 'server' or 'visitor'\", section.Name())\n}","typeGuard":"func isValidRole(role string) bool { return role == \"\" || role == \"server\" || role == \"visitor\" }","tryCatchPattern":null,"preventionTips":["Omit role for normal proxies; set it only for visitors","Use exact lowercase values","Lint configs for unknown role values"],"tags":["frp","config","ini","role","validation"],"backgroundTag":null,"analyzedSha":"6c8a8d0a97d03b44e9528d30b30c70cb9d61b405","analyzedAt":"2026-08-15T06:53:27.215Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}