跳到主要内容
ecdsa.com
Go crypto/tlsGo

错误信息

tls: first record does not look like a TLS handshake

gRPC、数据库驱动和其他库会把它包装进自己的消息中,因此要在日志中搜索这段核心文本。如果对方回复的是 HTTP,net/http 报出的则是“http: server gave HTTP response to HTTPS client”。

Go crypto/tls — the peer's first bytes are not a TLS record (tls.Dial, gRPC, database drivers, http.Client)

含义

Go 的 TLS 客户端发送了 ClientHello,但收到的第一条记录不具备 TLS 握手记录的结构,于是立即放弃。对方在这个端口上使用的是别的协议:纯 HTTP(在 Go 没能把它识别为 HTTP 的情况下)、明文 gRPC 服务器,或者数据库、邮件协议的问候消息。对于相反的情况,即客户端用明文访问 TLS 端口,Go 服务器记录的日志内容不同。

原因

修复方法

  1. 1.

    检查端口实际使用的协议

    用 openssl 做一次 TLS 握手,或者快速看一眼端口返回的横幅信息,就能知道这个端口到底是不是 TLS。

    bash
    # Does the port speak TLS?
    openssl s_client -connect api.example.com:8080 -servername api.example.com -brief </dev/null
    timeout 3 nc api.example.com 8080 </dev/null | head -1     # a text banner = plain protocol
  2. 2.

    gRPC:使用服务器要求的凭证

    TLS 服务器使用 TLS 凭证;不安全凭证(insecure)仅用于受信任网络中的明文服务器。

    go
    // gRPC: match the server. Plaintext server -> insecure credentials (only on trusted networks);
    // TLS server -> TLS credentials.
    conn, err := grpc.NewClient("api.example.com:9090",
    	grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{ServerName: "api.example.com"})))
  3. 3.

    在协议要求时使用 STARTTLS

    net/smtp 的 StartTLS(以及数据库驱动中的对应功能,例如 PostgreSQL 的 sslmode)会直接把现有连接升级为 TLS。

    go
    // SMTP on 587 starts in plain text: use STARTTLS, not tls.Dial
    c, err := smtp.Dial("mail.example.com:587")
    if err == nil {
    	err = c.StartTLS(&tls.Config{ServerName: "mail.example.com"})
    }

相关错误

浏览完整的签名错误库