AlexxIT/go2rtc · error
dtls: client handshake failed
Error message
dtls: client handshake failed: %w
What it means
connect performs the client-side PSK DTLS handshake on the main channel and wraps any NewDTLSClient failure with this error. It means the DTLS client handshake with the camera did not complete, so the session cannot be established.
Solutions
- Verify the PSK/credentials for the device are current
- Retry the dial — handshake failures over UDP are often transient
- Enable verbose DTLS logging to find the failing flight
- Check MTU/fragmentation issues on the path (handshake packets exceeding MTU)
Defensive patterns
Strategy: retry
Validate before calling
if psk == nil || len(psk) == 0 {
return fmt.Errorf("missing DTLS PSK for device")
} Try / catch
conn, err := dtls.DialDTLS(ctx, uid, psk)
if err != nil {
if strings.Contains(err.Error(), "client handshake failed") {
// transient UDP loss is common: re-dial with backoff
}
return err
} Prevention
- Refresh PSK after any device credential change
- Retry dial with exponential backoff for UDP handshake flakiness
- Check path MTU if handshake repeatedly fails at a specific flight
- Enable verbose DTLS logs when diagnosing
When it happens
Trigger: DialDTLS -> connect when NewDTLSServer/NewDTLSClient handshake fails: wrong PSK, handshake timeouts, UDP packet loss, or DTLS alert from the camera.
Common situations: PSK mismatch after a device password change; lossy WAN link dropping handshake flights; camera firmware with non-standard DTLS parameters; NAT rebinding mid-handshake.
Understand the failure class
- SSL/TLS and certificate errors — how TLS handshakes and certificate validation fail.
Related errors
- dtls: server handshake failed
- av login 2 failed
- wrong response:
- rtmp: wrong response %#v
- av login 1 failed
AI-assisted analysis of AlexxIT/go2rtc@c245815e75 (2026-09-07).
Data as JSON: /api/errors/852c4c5b14b530f4.
Report an issue: GitHub.
Appendix: source
Thrown at pkg/tutk/dtls/conn_dtls.go:569
}
func (c *DTLSConn) discoDoneCC51() error {
_, err := c.WriteAndWait(c.msgDiscoCC51(2, c.ticket, false), func(res []byte) bool {
if len(res) < packetSizeCC51 || string(res[:2]) != magicCC51 {
return false
}
cmd := binary.LittleEndian.Uint16(res[4:])
dir := binary.LittleEndian.Uint16(res[8:])
seq := binary.LittleEndian.Uint16(res[12:])
return cmd == cmdDiscoCC51 && dir == 0xFFFF && seq == 3
})
return err
}
func (c *DTLSConn) connect() error {
conn, err := NewDTLSClient(c.ctx, iotcChannelMain, c.addr, c.WriteDTLS, c.clientBuf, c.psk)
if err != nil {
return fmt.Errorf("dtls: client handshake failed: %w", err)
}
c.mu.Lock()
c.clientConn = conn
c.mu.Unlock()
if c.verbose {
fmt.Printf("[DTLS] Client handshake complete on channel %d\n", iotcChannelMain)
}
return nil
}
func (c *DTLSConn) worker() {
defer c.wg.Done()
buf := make([]byte, 2048)
View on GitHub (pinned to c245815e75)