{"record":{"id":"2e09207076332737","repo":"fatedier/frp","slug":"port-unavailable","errorCode":null,"errorMessage":"port unavailable","messagePattern":"port unavailable","errorType":"exception","errorClass":"ErrPortUnAvailable","httpStatus":null,"severity":"error","filePath":"server/ports/ports.go","lineNumber":25,"sourceCode":"\t\"sync\"\n\t\"time\"\n\n\t\"k8s.io/utils/clock\"\n\n\t\"github.com/fatedier/frp/pkg/config/types\"\n)\n\nconst (\n\tMinPort                    = 1\n\tMaxPort                    = 65535\n\tMaxPortReservedDuration    = time.Duration(24) * time.Hour\n\tCleanReservedPortsInterval = time.Hour\n)\n\nvar (\n\tErrPortAlreadyUsed = errors.New(\"port already used\")\n\tErrPortNotAllowed  = errors.New(\"port not allowed\")\n\tErrPortUnAvailable = errors.New(\"port unavailable\")\n\tErrNoAvailablePort = errors.New(\"no available port\")\n)\n\ntype PortCtx struct {\n\tProxyName  string\n\tPort       int\n\tClosed     bool\n\tUpdateTime time.Time\n}\n\ntype Manager struct {\n\treservedPorts map[string]*PortCtx\n\tusedPorts     map[int]*PortCtx\n\tfreePorts     map[int]struct{}\n\n\tbindAddr string\n\tnetType  string\n\tclock    clock.WithTicker","sourceCodeStart":7,"sourceCodeEnd":43,"githubUrl":"https://github.com/fatedier/frp/blob/6c8a8d0a97d03b44e9528d30b30c70cb9d61b405/server/ports/ports.go#L7-L43","documentation":"ErrPortUnAvailable is returned by ports.Manager.Acquire when the port is inside frps's allowed free pool but the live availability check fails — ports.Manager.isPortAvailable actually attempts net.Listen (or ListenUDP for udp) on the bind address, so the port is occupied at the OS level by a non-frp process, or the bind would fail (permissions, address in use).","triggerScenarios":"Another process on the frps host already listens on the requested port (nginx, sshd, another frps); requesting a privileged port below 1024 while frps runs unprivileged; UDP vs TCP type mismatch where the check protocol differs from the proxy's actual use; ephemeral-port collisions.","commonSituations":"frps host co-hosts other services; kernel ephemeral range overlapping allowPorts; SELinux/AppArmor denying the bind; IPv6/IPv4 bind address mismatch between the checker and the listener.","solutions":["On the frps host run ss -ltnp / ss -ulnp to find the process holding the port and stop it or pick another port","Choose a remotePort outside the kernel's ephemeral port range (cat /proc/sys/net/ipv4/ip_local_port_range)","For ports < 1024, run frps with CAP_NET_BIND_SERVICE or as root, or use a higher port","Retry after freeing the port — the check is live, so it passes once the port is actually bindable"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// Pre-flight on the frps host: confirm the port is actually bindable\nfunc portBindable(network, bindAddr string, port int) bool {\n    l, err := net.Listen(network, net.JoinHostPort(bindAddr, strconv.Itoa(port)))\n    if err != nil {\n        return false\n    }\n    l.Close()\n    return true\n}","typeGuard":"func isPortUnavailable(err error) bool {\n    return errors.Is(err, ports.ErrPortUnAvailable)\n}","tryCatchPattern":"realPort, err := pm.Acquire(name, port)\nif errors.Is(err, ports.ErrPortUnAvailable) {\n    // OS-level conflict: find and free the port, or choose another; a retry after freeing succeeds\n}","preventionTips":["Keep frps hosts dedicated to frp; scan for stray listeners after any co-hosted deployment","Exclude the kernel ephemeral range and ports below 1024 from allowPorts unless frps is privileged"],"tags":["frp","go","port","os","bind","server"],"backgroundTag":null,"analyzedSha":"6c8a8d0a97d03b44e9528d30b30c70cb9d61b405","analyzedAt":"2026-08-15T06:53:27.215Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}