本文へスキップ
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 がそれと認識できない場合)、平文の 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"})
    }

関連するエラー

署名エラーデータベースをすべて見る