derailed/k9s · error

port %s is not available on host

Error message

port %s is not available on host

What it means

PortTunnels.CheckAvailable re-validates an already-built tunnel list: for each tunnel it bind-tests (Address, LocalPort) via net.Listen (IsPortFree) and reports the first port that cannot be bound. Same root cause as error 162 but hit on saved/replayed tunnels rather than freshly parsed annotations.

Source

Thrown at internal/port/tunnel.go:22

package port

import (
	"context"
	"fmt"
	"log/slog"
	"net"

	"github.com/derailed/k9s/internal/slogs"
)

// PortTunnels represents a collection of tunnels.
type PortTunnels []PortTunnel

// CheckAvailable checks if all port tunnels are available.
func (t PortTunnels) CheckAvailable(ctx context.Context) error {
	for _, pt := range t {
		if !IsPortFree(ctx, pt) {
			return fmt.Errorf("port %s is not available on host", pt.LocalPort)
		}
	}

	return nil
}

// PortTunnel represents a host tunnel port mapper.
type PortTunnel struct {
	Address, Container, LocalPort, ContainerPort string
}

// NewPortTunnel returns a new instance.
func NewPortTunnel(a, co, lp, cp string) PortTunnel {
	return PortTunnel{
		Address:       a,
		Container:     co,
		LocalPort:     lp,
		ContainerPort: cp,

View on GitHub (pinned to 2d3ccc6ba2)

Solutions

  1. Identify the holder: lsof -i :<port> (or ss -ltnp | grep <port>) and stop it
  2. Change the tunnel's local port to a free one before re-checking
  3. Close other k9s sessions using the same ports
  4. Wait out TIME_WAIT (usually ~60s) if the previous listener just closed

Example fix

# before: saved tunnel 127.0.0.1:8080 blocked by stale process
lsof -ti :8080 | xargs kill
# after: re-run CheckAvailable -> passes
Defensive patterns

Strategy: fallback

Validate before calling

// Identify ALL conflicts up front (CheckAvailable stops at the first):
for _, pt := range tunnels {
	if !port.IsPortFree(ctx, pt) {
		log.Printf("conflict: %s", pt.String())
	}
}

Try / catch

Catch the per-tunnel error, report the conflicting LocalPort, and either skip that tunnel or remap it to a free port; continue with the rest of the list.

Prevention

When it happens

Trigger: Reconnecting port-forwards saved from a previous session whose local ports have since been taken; a race where another process binds the port between tunnel creation and the availability check; address not bindable (e.g. configured address not on host).

Common situations: Two k9s instances on the same workstation sharing fixed ports; a crashed session leaving sockets in TIME_WAIT or listeners behind; VPN/container tooling grabbing ports after resume.

Related errors


AI-assisted analysis of derailed/k9s@2d3ccc6ba2 (2026-08-15). Data as JSON: /api/errors/7936bdadca81b1c8. Report an issue: GitHub.