{"record":{"id":"42261bff411198cc","repo":"fatedier/frp","slug":"internal-error","errorCode":null,"errorMessage":"internal error","messagePattern":"internal error","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/ssh/gateway.go","lineNumber":79,"sourceCode":"\t\t\t\terr = os.WriteFile(cfg.AutoGenPrivateKeyPath, privateKeyBytes, 0o600)\n\t\t\t}\n\t\t}\n\t}\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tprivateKey, err := ssh.ParsePrivateKey(privateKeyBytes)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tsshConfig.AddHostKey(privateKey)\n\n\tsshConfig.NoClientAuth = cfg.AuthorizedKeysFile == \"\"\n\tsshConfig.PublicKeyCallback = func(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {\n\t\tauthorizedKeysMap, err := loadAuthorizedKeysFromFile(cfg.AuthorizedKeysFile)\n\t\tif err != nil {\n\t\t\tlog.Errorf(\"load authorized keys file error: %v\", err)\n\t\t\treturn nil, fmt.Errorf(\"internal error\")\n\t\t}\n\n\t\tuser, ok := authorizedKeysMap[string(key.Marshal())]\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"unknown public key for remoteAddr %q\", conn.RemoteAddr())\n\t\t}\n\t\treturn &ssh.Permissions{\n\t\t\tExtensions: map[string]string{\n\t\t\t\t\"user\": user,\n\t\t\t},\n\t\t}, nil\n\t}\n\n\tln, err := net.Listen(\"tcp\", net.JoinHostPort(bindAddr, strconv.Itoa(cfg.BindPort)))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &Gateway{","sourceCodeStart":61,"sourceCodeEnd":97,"githubUrl":"https://github.com/fatedier/frp/blob/6c8a8d0a97d03b44e9528d30b30c70cb9d61b405/pkg/ssh/gateway.go#L61-L97","documentation":"Returned by the SSH gateway's PublicKeyCallback when loadAuthorizedKeysFromFile fails while authenticating an incoming SSH connection. The message is intentionally generic ('internal error') so the client learns nothing about the server's filesystem, while the real cause (unreadable or unparseable authorized keys file) is written to the frps log via log.Errorf. Every SSH key-auth attempt re-reads the file, so a broken file rejects all key-based logins.","triggerScenarios":"cfg.AuthorizedKeysFile points to a file frps cannot read (missing path, permission denied, SELinux/AppArmor denial) or one containing malformed lines that make parsing fail. Any client connecting with `ssh -i key v0@server` then receives this error during authentication.","commonSituations":"AuthorizedKeysFile path typo in frps.toml; file owned by root while frps runs as a non-root service; empty or BOM-prefixed authorized_keys file; file deleted or rotated after frps start; containers where the file was not mounted into the expected path.","solutions":["Check the frps log for the 'load authorized keys file error: %v' line — it contains the real cause (ENOENT, EACCES, parse error).","Verify the path in the frps sshd-gateway AuthorizedKeysFile setting exists and is a readable regular file (ls -l, run as the frps user).","Fix permissions: the frps process user needs read access; in containers confirm the volume mount target matches the configured path.","Validate the file format: one entry per line, comments with #, keys in OpenSSH authorized_keys format.","Retry the SSH connection; the file is re-read on every auth attempt, so no frps restart is needed once fixed."],"exampleFix":"# frps.toml — before\n[sshServer]\nauthorizedKeysFile = \"/etc/frp/authorized_keys\"   # wrong path\n\n# after\n[sshServer]\nauthorizedKeysFile = \"/etc/frp/authorized_keys\"   # ensure: chmod 644, owned/readable by frps user, mounted in container","handlingStrategy":"validation","validationCode":"// before starting the frp SSH gateway, verify the authorized keys file\nfunc checkAuthorizedKeysFile(path string) error {\n    if path == \"\" {\n        return nil // NoClientAuth mode\n    }\n    f, err := os.Open(path)\n    if err != nil {\n        return fmt.Errorf(\"authorized keys file unreadable: %w\", err)\n    }\n    defer f.Close()\n    sc := bufio.NewScanner(f)\n    n := 0\n    for sc.Scan() {\n        line := strings.TrimSpace(sc.Text())\n        if line == \"\" || strings.HasPrefix(line, \"#\") {\n            continue\n        }\n        fields := strings.Fields(line)\n        if len(fields) < 2 {\n            return fmt.Errorf(\"malformed authorized keys line %d\", n+1)\n        }\n        n++\n    }\n    return sc.Err()\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Run frps as a user with read access to AuthorizedKeysFile; verify with `sudo -u frps cat <file>`.","Validate the file format in CI/deploy scripts (one 'type base64' line per key).","Containerize with an explicit read-only mount of the keys file so a missing mount fails loudly at start."],"tags":["ssh","authentication","configuration","file-permissions"],"backgroundTag":null,"analyzedSha":"6c8a8d0a97d03b44e9528d30b30c70cb9d61b405","analyzedAt":"2026-08-15T06:53:27.215Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}