fatedier/frp · critical
create control crypto read writer: %w
Error message
create control crypto read writer: %w
What it means
During the frpc-frps control connection login, after authentication succeeds the client upgrades the raw TCP conn to an encrypted read/writer via newControlReadWriter using the negotiated crypto context. This error wraps any failure of that crypto setup, whose real causes live in pkg/proto/wire: un-JSON-able hello transcripts, an unknown AEAD algorithm selected by the server, an algorithm the client never advertised, or a wrong-length server random.
Source
Thrown at client/control_session.go:90
loginMsg, err := d.buildLoginMsg(previousRunID)
if err != nil {
return nil, err
}
loginResult, err := d.exchangeLogin(conn, loginMsg)
if err != nil {
return nil, err
}
loginRespMsg := loginResult.resp
if loginRespMsg.Error != "" {
return nil, errors.New(loginRespMsg.Error)
}
var controlRW io.ReadWriter = conn
if d.clientSpec == nil || d.clientSpec.Type != "ssh-tunnel" {
controlRW, err = d.newControlReadWriter(conn, loginResult.crypto)
if err != nil {
return nil, fmt.Errorf("create control crypto read writer: %w", err)
}
}
success = true
return &SessionContext{
Common: d.common,
RunID: loginRespMsg.RunID,
Conn: msg.NewConn(conn, msg.NewReadWriter(controlRW, d.common.Transport.WireProtocol)),
Auth: d.auth,
Connector: newMessageConnector(connector, d.common.Transport.WireProtocol),
VnetController: d.vnetController,
UDPPacketCodec: loginResult.udpPacketCodec,
}, nil
}
func (d *controlSessionDialer) buildLoginMsg(previousRunID string) (*msg.Login, error) {
hostname, _ := os.Hostname()
loginMsg := &msg.Login{View on GitHub (pinned to 6c8a8d0a97)
Solutions
- Run matching frpc and frps versions (same release)
- Ensure nothing between client and server terminates or rewrites the control connection (no TLS-offloading proxies on the frps port)
- Capture the wrapped cause — 'unknown selected crypto algorithm' vs 'decode ... transcript' points to server build vs payload corruption
- Retry on a clean network path to rule out middlebox interference
Defensive patterns
Strategy: try-catch
Try / catch
_, err := svc.Login(ctx)
if err != nil && strings.Contains(err.Error(), "create control crypto read writer") {
// protocol/crypto mismatch with server: check version parity and network path
} Prevention
- Deploy frpc and frps from the same release
- Keep the control connection free of TLS-offloading middleboxes
- Log the wrapped cause — it distinguishes build mismatch from payload corruption
When it happens
Trigger: Login to a server whose ServerHello selects a crypto algorithm the client did not offer, or that sends malformed hello payloads; effectively only with a mismatched/modified frps build or a man-in-the-middle altering the handshake. Skipped entirely for ssh-tunnel client spec connections.
Common situations: frpc and frps built from different forks/versions with divergent crypto negotiation; a transparent proxy (corporate firewall, TLS-terminating LB) corrupting the binary handshake; extremely rare protocol-level incompatibility after a downgrade.
Related errors
- unexpected frame type %d, want %d
- loginRespMsg.Error
- serverHello.Error
- send ${op} request to plugin error
- invalid argument: frpc has no config file path
AI-assisted analysis of fatedier/frp@6c8a8d0a97 (2026-08-15).
Data as JSON: /api/errors/56207b507cff796c.
Report an issue: GitHub.