fatedier/frp · error

exactly one proxy type block is required

Error message

exactly one proxy type block is required

What it means

Returned by ProxyDefinition.Validate when the number of populated type blocks (tcp/udp/http/https/tcpmux/stcp/sudp/xtcp) in a proxy definition is not exactly one. Both zero blocks (only top-level name/type given) and two or more blocks (e.g. both "tcp" and "http" set) produce this error.

Source

Thrown at client/http/model/proxy_definition.go:37

	STCP   *v1.STCPProxyConfig   `json:"stcp,omitempty"`
	SUDP   *v1.SUDPProxyConfig   `json:"sudp,omitempty"`
	XTCP   *v1.XTCPProxyConfig   `json:"xtcp,omitempty"`
}

func (p *ProxyDefinition) Validate(pathName string, isUpdate bool) error {
	if strings.TrimSpace(p.Name) == "" {
		return fmt.Errorf("proxy name is required")
	}
	if !IsProxyType(p.Type) {
		return fmt.Errorf("invalid proxy type: %s", p.Type)
	}
	if isUpdate && pathName != "" && pathName != p.Name {
		return fmt.Errorf("proxy name in URL must match name in body")
	}

	_, blockType, blockCount := p.activeBlock()
	if blockCount != 1 {
		return fmt.Errorf("exactly one proxy type block is required")
	}
	if blockType != p.Type {
		return fmt.Errorf("proxy type block %q does not match type %q", blockType, p.Type)
	}
	return nil
}

func (p *ProxyDefinition) ToConfigurer() (v1.ProxyConfigurer, error) {
	block, _, _ := p.activeBlock()
	if block == nil {
		return nil, fmt.Errorf("exactly one proxy type block is required")
	}

	cfg := block
	cfg.GetBaseConfig().Name = p.Name
	cfg.GetBaseConfig().Type = p.Type
	return cfg, nil
}

View on GitHub (pinned to 6c8a8d0a97)

Solutions

  1. Include exactly one type block matching the "type" field, e.g. "tcp": {"localPort": 22, "remotePort": 6000} for type tcp.
  2. When changing proxy type, replace (not merge) the definition so the old block is absent/omitted (use omitempty semantics: null or drop the key).
  3. Validate the request body shape before sending: exactly one non-null block among the eight keys.

Example fix

// before
{"name": "ssh", "type": "tcp"}

// after
{"name": "ssh", "type": "tcp", "tcp": {"localPort": 22, "remotePort": 6000}}
Defensive patterns

Strategy: validation

Validate before calling

func countBlocks(def map[string]any) int {
    n := 0
    for _, k := range []string{"tcp","udp","http","https","tcpmux","stcp","sudp","xtcp"} {
        if def[k] != nil { n++ }
    }
    return n
}
// require countBlocks(def) == 1 before POST

Prevention

When it happens

Trigger: POST/PUT /api/proxies with {"name":"x","type":"tcp"} but no "tcp" block; or {"name":"x","type":"tcp","tcp":{...},"http":{...}} where two blocks are non-null.

Common situations: Forgetting the nested config block entirely; switching a proxy from http to tcp by changing "type" and adding the new block while forgetting to null out the old one; JSON merge of definitions accumulating blocks.

Related errors


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