fatedier/frp · error · ErrPortUnAvailable

port unavailable

Error message

port unavailable

What it means

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).

Source

Thrown at server/ports/ports.go:25

	"sync"
	"time"

	"k8s.io/utils/clock"

	"github.com/fatedier/frp/pkg/config/types"
)

const (
	MinPort                    = 1
	MaxPort                    = 65535
	MaxPortReservedDuration    = time.Duration(24) * time.Hour
	CleanReservedPortsInterval = time.Hour
)

var (
	ErrPortAlreadyUsed = errors.New("port already used")
	ErrPortNotAllowed  = errors.New("port not allowed")
	ErrPortUnAvailable = errors.New("port unavailable")
	ErrNoAvailablePort = errors.New("no available port")
)

type PortCtx struct {
	ProxyName  string
	Port       int
	Closed     bool
	UpdateTime time.Time
}

type Manager struct {
	reservedPorts map[string]*PortCtx
	usedPorts     map[int]*PortCtx
	freePorts     map[int]struct{}

	bindAddr string
	netType  string
	clock    clock.WithTicker

View on GitHub (pinned to 6c8a8d0a97)

Solutions

  1. On the frps host run ss -ltnp / ss -ulnp to find the process holding the port and stop it or pick another port
  2. Choose a remotePort outside the kernel's ephemeral port range (cat /proc/sys/net/ipv4/ip_local_port_range)
  3. For ports < 1024, run frps with CAP_NET_BIND_SERVICE or as root, or use a higher port
  4. Retry after freeing the port — the check is live, so it passes once the port is actually bindable
Defensive patterns

Strategy: retry

Validate before calling

// Pre-flight on the frps host: confirm the port is actually bindable
func portBindable(network, bindAddr string, port int) bool {
    l, err := net.Listen(network, net.JoinHostPort(bindAddr, strconv.Itoa(port)))
    if err != nil {
        return false
    }
    l.Close()
    return true
}

Type guard

func isPortUnavailable(err error) bool {
    return errors.Is(err, ports.ErrPortUnAvailable)
}

Try / catch

realPort, err := pm.Acquire(name, port)
if errors.Is(err, ports.ErrPortUnAvailable) {
    // OS-level conflict: find and free the port, or choose another; a retry after freeing succeeds
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of fatedier/frp@6c8a8d0a97 (2026-08-15). Data as JSON: /api/errors/2e09207076332737. Report an issue: GitHub.