fatedier/frp · error

local ports number should be same with remote ports number

Error message

local ports number should be same with remote ports number

What it means

Range-proxy validation: after util.ParseRangeNumbers expands both sides (single numbers, comma lists, and a-b ranges), the number of local ports must equal the number of remote ports so each local port pairs with exactly one remote port. A count mismatch aborts expansion, later surfacing as 'failed to render template for proxy ...' (error 151).

Solutions

  1. Count the ports each side expands to and make them equal
  2. For an a-b range of N ports, give the other side N values too (e.g. matching a-b range)
  3. Prefer symmetric ranges (6000-6002 / 7000-7002) to avoid manual counting

Example fix

; before
[range:web]
type = tcp
local_port = 6000-6003   ; 4 ports
remote_port = 7000-7002   ; 3 ports

; after
[range:web]
type = tcp
local_port = 6000-6003
remote_port = 7000-7003
Defensive patterns

Strategy: validation

Validate before calling

lp, err := util.ParseRangeNumbers(localStr); if err != nil { return err }
rp, err := util.ParseRangeNumbers(remoteStr); if err != nil { return err }
if len(lp) != len(rp) {
    return fmt.Errorf("local expands to %d ports, remote to %d; must match", len(lp), len(rp))
}

Prevention

When it happens

Trigger: local_port = '6000-6002' (3 ports) with remote_port = '7000-7001' (2 ports); or local_port = '6000,6001,6002' with remote_port = '7000,7002'. The error fires before any sections are generated.

Common situations: Editing one side of a range pair and forgetting the other; assuming frp cycles or truncates the shorter list; mixing a range on one side with a single value on the other.

Related errors


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

Appendix: source

Thrown at pkg/config/legacy/client.go:308

	// Validation
	localPortStr := section.Key("local_port").String()
	remotePortStr := section.Key("remote_port").String()
	if localPortStr == "" || remotePortStr == "" {
		return fmt.Errorf("local_port or remote_port is empty")
	}

	localPorts, err := util.ParseRangeNumbers(localPortStr)
	if err != nil {
		return err
	}

	remotePorts, err := util.ParseRangeNumbers(remotePortStr)
	if err != nil {
		return err
	}

	if len(localPorts) != len(remotePorts) {
		return fmt.Errorf("local ports number should be same with remote ports number")
	}

	if len(localPorts) == 0 {
		return fmt.Errorf("local_port and remote_port is necessary")
	}

	// Templates
	prefix := strings.TrimSpace(strings.TrimPrefix(section.Name(), "range:"))

	for i := range localPorts {
		tmpname := fmt.Sprintf("%s_%d", prefix, i)

		tmpsection, err := f.NewSection(tmpname)
		if err != nil {
			return err
		}

		copySection(section, tmpsection)

View on GitHub (pinned to 6c8a8d0a97)