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
- Use a stock, version-matched frpc so work connections skip the ClientHello
- If building a custom client, only send ClientHello on control/visitor connections, never on work connections
- Disable custom TLS wrapping of work connections and let frp's transport settings govern it
- 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
- Use stock frpc binaries for work connections
- Keep frpc/frps from the same release train
- If you fork frp, add an assertion in your dialer that work connections never perform a ClientHello
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
- token in NewWorkConn doesn't match token from configuration
- exec configuration is required when type is 'exec'
- file path cannot be empty
- exec command cannot be empty
- exec env name cannot be empty
AI-assisted analysis of fatedier/frp@6c8a8d0a97 (2026-08-15).
Data as JSON: /api/errors/e7566750ced64d55.
Report an issue: GitHub.