golang/go · error
tls: server's identity changed during renegotiation
Error message
tls: server's identity changed during renegotiation
What it means
During TLS renegotiation (TLS 1.2 and earlier; TLS 1.3 has none), Go requires the server's leaf certificate to be byte-identical (c.peerCertificates[0].Raw) to the one from the original handshake. This defeats 3SHAKE-style synchronization attacks (see mitls.org/pages/attacks/3SHAKE in the source comment). A different leaf on renegotiation is treated as an attack or a serious server-side inconsistency.
Source
Thrown at src/crypto/tls/handshake_client.go:696
}
}
if c.handshakes == 0 {
// If this is the first handshake on a connection, process and
// (optionally) verify the server's certificates.
if err := c.verifyServerCertificate(certMsg.certificates); err != nil {
return err
}
} else {
// This is a renegotiation handshake. We require that the
// server's identity (i.e. leaf certificate) is unchanged and
// thus any previous trust decision is still valid.
//
// See https://mitls.org/pages/attacks/3SHAKE for the
// motivation behind this requirement.
if !bytes.Equal(c.peerCertificates[0].Raw, certMsg.certificates[0]) {
c.sendAlert(alertBadCertificate)
return errors.New("tls: server's identity changed during renegotiation")
}
}
keyAgreement := hs.suite.ka(c.vers)
skx, ok := msg.(*serverKeyExchangeMsg)
if ok {
err = keyAgreement.processServerKeyExchange(c.config, hs.hello, hs.serverHello, c.peerCertificates[0], skx)
if err != nil {
c.sendAlert(alertIllegalParameter)
return err
}
if keyAgreement, ok := keyAgreement.(*ecdheKeyAgreement); ok {
c.curveID = keyAgreement.curveID
c.peerSigAlg = keyAgreement.signatureAlgorithm
}
msg, err = c.readHandshake(&hs.finishedHash)View on GitHub (pinned to b6b368adc5)
Solutions
- Prefer TLS 1.3 (no renegotiation) by setting MinVersion = VersionTLS13.
- Ensure every node in the server pool serves the identical leaf certificate.
- Coordinate certificate rotation so it does not span active renegotiating connections.
- Reduce connection lifetime below the rotation interval.
Example fix
// before: allows renegotiation on long-lived TLS 1.2 conn
cfg := &tls.Config{Renegotiation: tls.RenegotiateFreelyAsClient}
// after: move to TLS 1.3 where renegotiation does not exist
cfg := &tls.Config{MinVersion: tls.VersionTLS13} Defensive patterns
Strategy: validation
Validate before calling
// Avoid renegotiation entirely by requiring TLS 1.3.
func avoidRenegotiation(cfg *tls.Config) {
cfg.MinVersion = tls.VersionTLS13
} Type guard
func isIdentityChangedRenegotiation(err error) bool {
return err != nil && strings.Contains(err.Error(), "server's identity changed during renegotiation")
} Try / catch
if _, err := tls.Dial("tcp", addr, cfg); err != nil {
if isIdentityChangedRenegotiation(err) {
// The server cluster is serving inconsistent certs; cannot safely retry.
reportClusterInconsistency(addr, err)
}
} Prevention
- Set MinVersion = VersionTLS13 to eliminate renegotiation.
- Keep server-pool leaf certificates uniform.
- Coordinate cert rotation outside active long-lived connections.
When it happens
Trigger: Server rotated its certificate between the original handshake and a renegotiation; server pool behind a load balancer serving different leaf certs per node; renegotiation triggered by IIS demanding a client certificate mid-connection.
Common situations: Long-lived connections spanning a certificate renewal window; clusters with non-uniform certificates; Windows IIS renegotiation-for-client-cert flows.
Understand the failure class
- SSL/TLS and certificate errors — how TLS handshakes and certificate validation fail.
Related errors
- tls: initial handshake had non-empty renegotiation extension
- tls: incorrect renegotiation extension contents
- tls: invalid outer extensions
- tls: malformed encrypted_client_hello extension
- tls: short read from Rand:
AI-assisted analysis of golang/go@b6b368adc5 (2026-08-12).
Data as JSON: /api/errors/ee3e480abc606bcf.
Report an issue: GitHub.