fatedier/frp · error

ClientHello is not allowed on work connections

Error message

ClientHello is not allowed on work connections

What it means

RegisterWorkConn rejects a work connection on which the client performed a TLS-style ClientHello exchange. Work connections reuse the credentials negotiated on the control connection and must not carry their own handshake; only the control/visitor paths may present a ClientHello. Sending one indicates a protocol-state bug or an incompatible/hand-rolled client.

Source

Thrown at server/service.go:857

	if err != nil {
		return ctl, err
	}
	if !active {
		return ctl, errControlReplaced
	}

	return ctl, nil
}

// RegisterWorkConn register a new work connection to control and proxies need it.
func (svr *Service) RegisterWorkConn(
	workConn *msg.Conn,
	newMsg *msg.NewWorkConn,
	workWireProtocol string,
	workClientHelloPresent bool,
) error {
	if workClientHelloPresent {
		return fmt.Errorf("ClientHello is not allowed on work connections")
	}
	xl := netpkg.NewLogFromConn(workConn)
	ctl, exist := svr.ctlManager.GetByID(newMsg.RunID)
	if !exist {
		xl.Warnf("no client control found for run id [%s]", newMsg.RunID)
		return fmt.Errorf("no client control found for run id [%s]", newMsg.RunID)
	}
	if workWireProtocol != ctl.sessionCtx.WireProtocol {
		return fmt.Errorf("work connection wire protocol mismatch: got %s want %s", workWireProtocol, ctl.sessionCtx.WireProtocol)
	}

	// server plugin hook
	content := &plugin.NewWorkConnContent{
		User: plugin.UserInfo{
			User:  ctl.sessionCtx.LoginMsg.User,
			Metas: ctl.sessionCtx.LoginMsg.Metas,
			RunID: ctl.sessionCtx.LoginMsg.RunID,
		},

View on GitHub (pinned to 6c8a8d0a97)

Solutions

  1. Use a stock, version-matched frpc so work connections skip the ClientHello
  2. If building a custom client, only send ClientHello on control/visitor connections, never on work connections
  3. Disable custom TLS wrapping of work connections and let frp's transport settings govern it
  4. Upgrade both sides to releases whose work-connection protocol matches
Defensive patterns

Strategy: validation

Validate before calling

// custom client: never send ClientHello on work connections
const (
    onControl  = true
    onWorkConn = false
)
// dialWorkConn(path, hello=onWorkConn) // hello must be false

Prevention

When it happens

Trigger: Calling RegisterWorkConn with workClientHelloPresent=true — i.e. the server detected a ClientHello message on the work connection before the NewWorkConn message. Happens with modified frpc builds, custom clients that apply TLS to every connection, or version combinations where the work-connection handshake semantics changed.

Common situations: Custom client implementations that enable a client hello on all connections; a misconfigured TLS layer wrapping work connections; frpc/frps built from mismatched forks; middleboxes injecting a TLS handshake into the stream.

Related errors


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